Compare commits

...

62 commits

Author SHA1 Message Date
Jonas 'Sortie' Termansen
cef7779f31 Handle SIGTERM in display(1).
Display a final frame with a message explaining what is happening that is
displayed while the system powers off, reboots, halts, when the user logs
out, or just exits the compositor.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
36a2d0ccc3 Sync login framebuffer code to libui. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
a180902247 Handle SIGTERM in login(8).
Display a final frame with a message explaining what is happening that is
displayed while the system powers off, reboots, or halts.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
5633699695 Modernize process and thread member variable names. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
e3af5ae322 Fix ppoll(2) EINTR handling. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
63a6509396 Add pstree(1) -is options. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
9d287d9433 Add chroot(8) -I option. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
7eb6a5f517 Modernize chroot(8). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
3cb2bf4590 Prevent init(8) from using the tty when a daemon uses it. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
e88c1ca56c Run daemons in their own session. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
035cd9b1a2 Add init groups.
Every process now has an init process like it has a session, and each
session belong to an init. Orphaned processes are reparented to its init
process. All descendent processes are SIGKILL'd when an init process exits
and creating additional processes/threads fails.

Add setinit(2) for becoming the init process for your child processes and
add getinit(2) for locating your init process.

Add TIOCSCTTY force flag that releases a terminal from its current session
and makes it the controlling terminal for the current session. This ioctl
is essential to transferring the controlling terminal to a nested init,
which has its own session.

Add TIOCUCTTY that releases the terminal as the controlling terminal for
its current session.

Remove INIT_PID as it is replaced by getinit(2).
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
189e79d62d Add service(8). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
08a834d932 Add checksum(1) --cache option. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
504415bc22 Add php port. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
7de016539a Revert "Update to perl-5.39.5."
This reverts commit 781ff8880f3e2d29e31460427bccf50cc1ec436e.

File/Spec was no longer being installed when --all-static, breaking
texinfo.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
bc2cf40dab Update to perl-5.39.5. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
e76b19fd2b Revert "Update to python-3.12.1."
This reverts commit 8ae8363167cc195f92803489e2f97391e2527c5f.

The libglib build broke due to no distutils module.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
5cec2e2f55 Update to python-3.12.1. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
4ba880938e Revert "Update to texinfo-7.1."
This reverts commit 9813bb1d13c625d48ecd950bfaafc274383ca049.

ffmpeg fails to build natively with the new makeinfo:

perl ./doc/texidep.pl . doc/ffmpeg-utils.texi doc/ffmpeg-utils.html >doc/ffmpeg-utils.html.d
makeinfo --html -I doc --no-split -D config-not-all --init-file=./doc/t2h.pm --output doc/ffmpeg-utils.html doc/ffmpeg-utils.texi
makeinfo: error parsing ./doc/t2h.pm: Undefined subroutine &Texinfo::Config::set_from_init_file called at ./doc/t2h.pm line 24.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
005eb40d27 Update to texinfo-7.1. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
adb08e530d Revert "Update to dash-0.5.12."
This reverts commit 16bedfc9630779c01ebae5513fd307e969c329de.

Something is wrong with the case pattern matching, maybe fnmatch?
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
544b52583e Update to dash-0.5.12. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
8f879f75a3 Revert "Update to freetype-2.13.2."
This reverts commit 5ad1e5f6054dd1e44ecb955b9326c1198ef17ff0.

configure runs make and crashes on a stack overflow in make.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
e5ae1e62e3 Update to freetype-2.13.2. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
6b158f7795 Revert "Update to bison-3.8.2."
This reverts commit b82fae810b42c5426d21c4dc153b32f086dd7fde.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
e74efce270 Update to bison-3.8.2. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
372370f37d Revert "Schedule interactive threads fairly under load."
This reverts commit 47731b91c37933943a73010c5a4494101cce52dc.

There is a rare freeze when the scheduler fails to run anything.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
8826615b48 Schedule interactive threads fairly under load.
Preempted threads are now removed from the runnable list until every
other thread has been preempted or the system goes idle. This ensures
threads that roundtrip to other threads get a full chance to perform
their work cooperatively without being starved by CPU intensive threads
whenever they yield.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
1e2a1f155a Kinda fix pager(1) man bullet points. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
257b3abe40 Fix ESP endian. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
4ea8396b5d Support booting with EFI. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
0871fa71c3 Add kernel(7) --firmware option. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
f9a346a6dc Add fatfs(8). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
ffafa81cd0 Add getty(8). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
0ab8901f8b Add terminal and interrupt support to com(4). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
8f4c3371f5 Add nyan(1). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
e9bfbc8d03 Work around pty deadlock. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
456e2ac596 Add cdrom mounting live environment. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
faff59cffc Revert "Parallelize driver initialization."
This reverts commit 0fef08bbc4.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
06fcbdb6ae Parallelize driver initialization. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
3fe2a32914 Speed up ata(4) 400 ns waits.
Waiting for any non-zero duration currently waits for at least one timer
cycle (10 ms), which is especially expensive during early boot.

The current workaround of simply reading the status 14 times seems really
suspicious although the osdev wiki documents it, but let's see how well it
works on real hardware, it's probably good enough.

Try to determine the initial selected drive to save one drive selection.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
a9b15bffe6 Decrease PS/2 timeouts. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
35e68109c9 Add uptime(1) -pr options. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
5248220e40 Add iso9660 filesystem implementation. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
35f4d2f000 Add kernel virtual address space usage debug information. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
9d317c462e Debug TCP socket state listing. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
250b95616b Add kernel heap allocation tracing debug facility. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
204576b7a8 Trianglix 4. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
8e3e058fd6 Add tix-check(8). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
bf2d489da1 Volatile release. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
d3d339b616 Add tix-upgrade(8). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
83ac75a083 Add tix-repository(8).
Support renaming, splitting, and deleting ports via RENAMES.

Unify on RUNTIME_DEPS for runtime dependencies.
2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
cc182e7912 Add signify port. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
d75c716891 Add pty(1). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
704ddbe061 Add getaddrinfo(1). 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
96d4615aee Enable stack smash protection by default. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
12a69346a0 Enable undefined behavior sanitization by default. 2024-06-22 17:26:05 +02:00
Jonas 'Sortie' Termansen
eede0df814 Handle SIGHUP in sh(1) and save shell history. 2024-06-22 17:26:04 +02:00
Jonas 'Sortie' Termansen
bab61702f3 Handle SIGWINCH in editor(1). 2024-06-22 17:23:51 +02:00
Jonas 'Sortie' Termansen
bf1d15957e Implement SIGWINCH. 2024-06-22 17:23:51 +02:00
Jonas 'Sortie' Termansen
ecd5217da8 Separate filesystem socket namespace inside chroots. 2024-06-22 17:23:51 +02:00
Jonas 'Sortie' Termansen
735dffd029 Fix sh(1) looping endlessly on input errors. 2024-06-22 17:23:51 +02:00
193 changed files with 16498 additions and 896 deletions

View file

@ -20,13 +20,16 @@ display \
dnsconfig \
editor \
ext \
fat \
games \
hostname \
ifconfig \
init \
iso9660 \
kblayout \
kblayout-compiler \
login \
nyan \
ping \
regress \
rw \
@ -68,10 +71,13 @@ else
SORTIX_INCLUDE_SOURCE?=yes
endif
ISO_MOUNT?=no
include build-aux/dirs.mak
BUILD_NAME:=sortix-$(RELEASE)-$(MACHINE)
CHAIN_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).chain.tar
LIVE_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).live.tar
OVERLAY_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).overlay.tar
SRC_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).src.tar
@ -226,11 +232,13 @@ sysroot-system: sysroot-fsh sysroot-base-headers
echo 'ID=sortix' && \
echo 'VERSION_ID="$(VERSION)"' && \
echo 'PRETTY_NAME="Sortix $(VERSION)"' && \
echo 'SORTIX_ABI=2.0' && \
echo 'SORTIX_ABI=2.1' && \
true) > "$(SYSROOT)/etc/sortix-release"
echo /etc/sortix-release >> "$(SYSROOT)/tix/manifest/system"
ln -sf sortix-release "$(SYSROOT)/etc/os-release"
echo /etc/os-release >> "$(SYSROOT)/tix/manifest/system"
find etc | sed -e 's,^,/,' >> "$(SYSROOT)/tix/manifest/system"
cp -RT etc "$(SYSROOT)/etc"
find share | sed -e 's,^,/,' >> "$(SYSROOT)/tix/manifest/system"
cp -RT share "$(SYSROOT)/share"
export SYSROOT="$(SYSROOT)" && \
@ -269,6 +277,7 @@ else ifneq ($(SORTIX_INCLUDE_SOURCE),no)
cp Makefile -t "$(SYSROOT)/src"
cp README -t "$(SYSROOT)/src"
cp -RT build-aux "$(SYSROOT)/src/build-aux"
cp -RT etc "$(SYSROOT)/src/etc"
cp -RT share "$(SYSROOT)/src/share"
(for D in $(MODULES); do (cp -R $$D -t "$(SYSROOT)/src" && $(MAKE) -C "$(SYSROOT)/src/$$D" clean) || exit $$?; done)
endif
@ -441,6 +450,25 @@ release-all-archs:
# Initial ramdisk
$(CHAIN_INITRD).uuid:
mkdir -p `dirname $@`
uuidgen > $@
$(CHAIN_INITRD): $(CHAIN_INITRD).uuid sysroot
mkdir -p `dirname $(CHAIN_INITRD)`
rm -rf $(CHAIN_INITRD).d
mkdir -p $(CHAIN_INITRD).d
mkdir -p $(CHAIN_INITRD).d/etc
echo "UUID=`cat $(CHAIN_INITRD).uuid` / iso9660 ro 0 1" > $(CHAIN_INITRD).d/etc/fstab
mkdir -p $(CHAIN_INITRD).d/etc/init
echo require chain exit-code > $(CHAIN_INITRD).d/etc/init/default
mkdir -p $(CHAIN_INITRD).d/sbin
cp "$(SYSROOT)/sbin/init" $(CHAIN_INITRD).d/sbin
cp "$(SYSROOT)/sbin/iso9660fs" $(CHAIN_INITRD).d/sbin
LC_ALL=C ls -A $(CHAIN_INITRD).d | \
tar -cf $(CHAIN_INITRD) -C $(CHAIN_INITRD).d --numeric-owner --owner=0 --group=0 -T -
rm -rf $(CHAIN_INITRD).d
$(LIVE_INITRD): sysroot
mkdir -p `dirname $(LIVE_INITRD)`
rm -rf $(LIVE_INITRD).d
@ -452,6 +480,9 @@ $(LIVE_INITRD): sysroot
echo "include /etc/default/passwd.d/*" >> $(LIVE_INITRD).d/etc/passwd
echo "root::0:root" > $(LIVE_INITRD).d/etc/group
echo "include /etc/default/group.d/*" >> $(LIVE_INITRD).d/etc/group
(echo 'channel = $(CHANNEL)' && \
echo 'release_key = $(RELEASE_KEY)' && \
echo 'release_sig_url = $(RELEASE_SIG_URL)') > $(LIVE_INITRD).d/etc/upgrade.conf
mkdir -p $(LIVE_INITRD).d/root -m 700
cp -RT "$(SYSROOT)/etc/skel" $(LIVE_INITRD).d/root
(echo "You can view the documentation for new users by typing:" && \
@ -490,6 +521,42 @@ $(SORTIX_BUILDS_DIR):
# Bootable images
ifeq ($(ISO_MOUNT),yes)
$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso: sysroot $(CHAIN_INITRD) $(CHAIN_INITRD).uuid $(SORTIX_BUILDS_DIR)
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot
cp "$(SYSROOT)/boot/sortix.bin" $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot/sortix.bin
cp $(CHAIN_INITRD) $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot/sortix.initrd
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc
echo "UUID=`cat $(CHAIN_INITRD).uuid` / iso9660 ro 0 1" > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/fstab
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/init
echo require single-user exit-code > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/init/default
echo "root::0:0:root:/root:sh" > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/passwd
echo "include /etc/default/passwd.d/*" >> $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/passwd
echo "root::0:root" > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/group
echo "include /etc/default/group.d/*" >> $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/group
(echo 'channel = $(CHANNEL)' && \
echo 'release_key = $(RELEASE_KEY)' && \
echo 'release_sig_url = $(RELEASE_SIG_URL)') > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/upgrade.conf
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/root -m 700
cp -RT "$(SYSROOT)/etc/skel" $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/root
(echo "You can view the documentation for new users by typing:" && \
echo && \
echo " man user-guide" && \
echo && \
echo "You can view the installation instructions by typing:" && \
echo && \
echo " man installation") > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/root/welcome
if [ -d "$(SYSROOT_OVERLAY)" ]; then cp -RT "$(SYSROOT_OVERLAY)" $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso; fi
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot/grub
build-aux/iso-grub-cfg.sh --platform $(HOST) --version $(VERSION) --mount $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
grub-mkrescue -o $(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso sysroot $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso -- -volid Sortix -volset_id `cat $(CHAIN_INITRD).uuid`
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
else
$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso: sysroot $(LIVE_INITRD) $(OVERLAY_INITRD) $(SRC_INITRD) $(SYSTEM_INITRD) $(SORTIX_BUILDS_DIR)
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
@ -530,6 +597,8 @@ else # none
endif
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
endif
.PHONY: iso
iso: $(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso
@ -618,6 +687,7 @@ release-repository: sysroot $(SYSTEM_INITRD) $(SORTIX_RELEASE_DIR)/$(RELEASE)/re
cp $(SORTIX_REPOSITORY_DIR)/$(HOST)/$$port.tix.tar.xz $(SORTIX_RELEASE_DIR)/$(RELEASE)/repository/$(HOST) && \
cp $(SORTIX_REPOSITORY_DIR)/$(HOST)/$$port.version $(SORTIX_RELEASE_DIR)/$(RELEASE)/repository/$(HOST); \
done
tix-repository --generation=3 $(SORTIX_RELEASE_DIR)/$(RELEASE)/repository/$(HOST)
.PHONY: release-scripts
release-scripts: \

View file

@ -124,9 +124,9 @@ endif
DEFAULT_GENERIC_OPTLEVEL_BASE:=-Os -s
DEFAULT_OPTLEVEL_FOR_BUILD:=$(DEFAULT_GENERIC_OPTLEVEL_BASE)
ifeq ($(BUILD_IS_SORTIX),1)
DEFAULT_OPTLEVEL_FOR_BUILD+=
DEFAULT_OPTLEVEL_FOR_BUILD+=-fsanitize=undefined -fstack-protector-all
endif
DEFAULT_OPTLEVEL:=$(DEFAULT_GENERIC_OPTLEVEL_BASE)
ifeq ($(HOST_IS_SORTIX),1)
DEFAULT_OPTLEVEL+=
DEFAULT_OPTLEVEL+=-fsanitize=undefined -fstack-protector-all
endif

View file

@ -23,8 +23,9 @@ set -e
this=$(which -- "$0")
thisdir=$(dirname -- "$this")
platform=
directory=
mount=false
platform=
version=
dashdash=
@ -44,8 +45,9 @@ for argument do
case $dashdash$argument in
--) dashdash=yes ;;
--platform=*) platform=$parameter ;;
--platform) previous_option=platform ;;
--platform=*) platform=$parameter ;;
--mount) mount=true ;;
--version=*) version=$parameter ;;
--version) previous_option=version ;;
-*) echo "$0: unrecognized option $argument" >&2
@ -111,14 +113,24 @@ isinset() {
cd "$directory"
kernel=$(maybe_compressed boot/sortix.bin)
live_initrd=$(maybe_compressed boot/live.tar)
overlay_initrd=$(maybe_compressed boot/overlay.tar)
src_initrd=$(maybe_compressed boot/src.tar)
system_initrd=$(maybe_compressed repository/system.tix.tar)
ports=$(ls repository |
grep -E '\.tix\.tar\.xz$' |
sed -E 's/\.tix\.tar\.xz$//' |
(grep -Ev '^system$' || true))
if $mount; then
initrd=$(maybe_compressed boot/sortix.initrd)
initrds=$initrd
else
live_initrd=$(maybe_compressed boot/live.tar)
overlay_initrd=$(maybe_compressed boot/overlay.tar)
src_initrd=$(maybe_compressed boot/src.tar)
system_initrd=$(maybe_compressed repository/system.tix.tar)
initrds="$system_initrd $src_initrd $live_initrd $overlay_initrd"
fi
if $mount; then
ports=
else
ports=$(ls repository |
grep -E '\.tix\.tar\.xz$' |
sed -E 's/\.tix\.tar\.xz$//' |
(grep -Ev '^system$' || true))
fi
mkdir -p boot/grub
mkdir -p boot/grub/init
@ -184,6 +196,12 @@ fi
set version="$version"
set machine="$machine"
set mount=$mount
if \$mount; then
chain='-- /sbin/init --target=chain'
else
chain=
fi
set base_menu_title="Sortix \$version for \$machine"
set menu_title="\$base_menu_title"
set title_single_user='live environment'
@ -205,6 +223,8 @@ set enable_sshd=false
export version
export machine
export mount
export chain
export base_menu_title
export menu_title
export title_single_user
@ -305,9 +325,10 @@ esac
cat << EOF
hook_kernel_pre
echo -n "Loading /$kernel ($(human_size $kernel)) ... "
multiboot /$kernel \$no_random_seed \$enable_network_drivers "\$@"
multiboot /$kernel --firmware=\$grub_platform \$no_random_seed \$enable_network_drivers \$chain "\$@"
echo done
hook_kernel_post
# TODO: Injecting configuration doesn't work for mounted cdroms.
if ! \$enable_dhclient; then
echo -n "Disabling dhclient ... "
module /boot/grub/init/furthermore --create-to /etc/init/network
@ -332,7 +353,7 @@ cat << EOF
echo done
fi
EOF
for initrd in $system_initrd $src_initrd $live_initrd $overlay_initrd; do
for initrd in $initrds; do
if [ "$initrd" = "$src_initrd" ]; then
cat << EOF
if \$enable_src; then
@ -428,9 +449,11 @@ menuentry "\$title_sysupgrade" '-- /sbin/init --target=sysupgrade'
cat << EOF
if ! $mount; then
menuentry "Select ports..." {
configfile /boot/grub/ports.cfg
}
fi
menuentry "Advanced..." {
configfile /boot/grub/advanced.cfg
@ -462,6 +485,7 @@ else
}
fi
if ! $mount; then
if "\$enable_src"; then
menuentry "Disable loading source code" {
enable_src=false
@ -473,6 +497,7 @@ else
configfile /boot/grub/advanced.cfg
}
fi
fi
if [ "\$enable_network_drivers" = --disable-network-drivers ]; then
menuentry "Enable networking drivers" {
@ -486,6 +511,7 @@ else
}
fi
if ! $mount; then
if \$enable_dhclient; then
menuentry "Disable DHCP client" {
enable_dhclient=false
@ -525,6 +551,7 @@ fi
menuentry "Select binary packages..." {
configfile /boot/grub/tix.cfg
}
fi
hook_advanced_menu_post
EOF

View file

@ -1,3 +1,3 @@
set_minimal="cut dash e2fsprogs grep grub mdocml sed xargs"
set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip irssi libcurl libcurses libssl libstdc++ m4 make nano ntpd patch perl pkg-config python ssh tar texinfo vim wget xz xorriso"
set_minimal="cut dash dosfstools e2fsprogs grep grub grub-i386-pc grub-i386-efi grub-x86_64-efi libssl mdocml sed signify tar wget xargs xz"
set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip irssi libcurl libcurses libstdc++ m4 make mtools nano ntpd patch perl pkg-config python ssh texinfo vim xorriso"
sets="basic minimal"

View file

@ -1,2 +1,6 @@
VERSION=1.1dev
CHANNEL?=volatile
RELEASE?=$(VERSION)
RELEASE_MASTER?=https://sortix.org/release
RELEASE_KEY=/etc/signify/sortix-$(VERSION).pub
RELEASE_SIG_URL?=$(RELEASE_MASTER)/$(CHANNEL)/$(HOST_MACHINE).sh.sig

View file

@ -1,4 +1,3 @@
SOFTWARE_MEANT_FOR_SORTIX=1
include ../build-aux/platform.mak
include ../build-aux/compiler.mak
include ../build-aux/version.mak

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2020 Jonas 'Sortie' Termansen.
* Copyright (c) 2017, 2020, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -17,6 +17,8 @@
* Compute and check cryptographic hashes.
*/
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
@ -97,8 +99,20 @@ static struct hash* hashes[] =
NULL,
};
struct checklist
{
const char* file;
uint8_t checksum[DIGEST_MAX_LENGTH];
bool initialized;
};
static struct checklist** cache = NULL;
static size_t cache_used = 0;
static size_t cache_length = 0;
static struct timespec cache_time;
static struct hash* hash = NULL;
static const char* algorithm = NULL;
static const char* cache_path = NULL;
static const char* checklist = NULL;
static bool check = false;
static bool ignore_missing = false;
@ -116,19 +130,182 @@ int debase(char c)
return -1;
}
static void printhex(const uint8_t* buffer, size_t size)
static void fprinthex(FILE* fp, const uint8_t* buffer, size_t size)
{
for ( size_t i = 0; i < size; i++ )
{
putchar(hexchars[buffer[i] >> 4]);
putchar(hexchars[buffer[i] & 0xF]);
fputc(hexchars[buffer[i] >> 4], fp);
fputc(hexchars[buffer[i] & 0xF], fp);
}
}
static int compare_checklist_file(const void* a_ptr, const void* b_ptr)
{
struct checklist* a = *(struct checklist**) a_ptr;
struct checklist* b = *(struct checklist**) b_ptr;
return strcmp(a->file, b->file);
}
static int search_checklist_file(const void* file_ptr, const void* elem_ptr)
{
const char* file = (const char*) file_ptr;
struct checklist* elem = *(struct checklist**) elem_ptr;
return strcmp(file, elem->file);
}
static struct checklist* checklist_lookup(struct checklist** sorted,
size_t used,
const char* file)
{
struct checklist** entry_ptr =
bsearch(file, sorted, used,
sizeof(struct checksum*), search_checklist_file);
return entry_ptr ? *entry_ptr : NULL;
}
static void checklist_add(struct checklist*** checklist_ptr,
size_t* used_ptr,
size_t* length_ptr,
uint8_t digest[DIGEST_MAX_LENGTH],
const char* file)
{
struct checklist* entry = calloc(1, sizeof(struct checklist));
if ( !entry || !(entry->file = strdup(file)) )
err(1, "malloc");
memcpy(entry->checksum, digest, hash->digest_size);
if ( *used_ptr == *length_ptr )
{
struct checklist** new_checklist =
reallocarray(*checklist_ptr, *length_ptr,
2 * sizeof(struct checklist*));
if ( !new_checklist )
err(1, "malloc");
*checklist_ptr = new_checklist;
*length_ptr *= 2;
}
(*checklist_ptr)[(*used_ptr)++] = entry;
}
static void checklist_parse(char* line,
size_t line_length,
struct checklist* entry,
const char* path,
off_t line_number)
{
if ( line[line_length - 1] != '\n' )
errx(1, "%s:%ji: Line was not terminated with a newline",
path, (intmax_t) line_number);
line[--line_length] = '\0';
if ( (size_t) line_length < 2 * hash->digest_size )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
for ( size_t i = 0; i < hash->digest_size; i++ )
{
int higher = debase(line[i*2 + 0]);
int lower = debase(line[i*2 + 1]);
if ( higher == -1 || lower == -1 )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
entry->checksum[i] = higher << 4 | lower;
}
if ( line[2 * hash->digest_size + 0] != ' ' ||
line[2 * hash->digest_size + 1] != ' ' ||
line[2 * hash->digest_size + 2] == '\0' )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
entry->file = line + 2 * hash->digest_size + 2;
if ( !strcmp(path, "-") && !strcmp(entry->file, "-") )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
}
static void checklist_read(struct checklist*** ptr,
size_t* used_ptr,
size_t* length_ptr,
struct timespec* time_ptr,
const char* path,
bool allow_missing)
{
if ( !(*ptr = malloc(sizeof(struct checklist*) * 4)) )
err(1, "malloc");
*used_ptr = 0;
*length_ptr = 4;
FILE* fp = fopen(path, "r");
if ( !fp )
{
if ( allow_missing )
return;
err(1, "malloc");
}
struct stat st;
fstat(fileno(fp), &st);
*time_ptr = st.st_mtim;
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
off_t line_number = 0;
struct checklist input;
while ( 0 < (line_length = getline(&line, &line_size, fp)) )
{
line_number++;
checklist_parse(line, (size_t) line_length, &input, path, line_number);
checklist_add(ptr, used_ptr, length_ptr, input.checksum, input.file);
}
free(line);
if ( ferror(fp) )
err(1, "%s", path);
fclose(fp);
qsort(*ptr, *used_ptr, sizeof(struct checklist*), compare_checklist_file);
}
static void checklist_write(struct checklist** checklist,
size_t used,
const char* path)
{
char* out_path;
if ( asprintf(&out_path, "%s.XXXXXX", path) < 0 )
err(1, "malloc");
int out_fd = mkstemp(out_path);
if ( out_fd < 0 )
err(1, "mkstemp: %s.XXXXXX", path);
FILE* out = fdopen(out_fd, "w");
if ( !out )
{
unlink(out_path);
err(1, "fdopen");
}
for ( size_t i = 0; i < used; i++ )
{
fprinthex(out, checklist[i]->checksum, hash->digest_size);
fprintf(out, " %s\n", checklist[i]->file);
}
if ( ferror(out) || fclose(out) == EOF )
{
unlink(out_path);
err(1, "%s", out_path);
}
if ( rename(out_path, path) < 0 )
err(1, "rename: %s -> %s", out_path, path);
free(out_path);
}
static int digest_fd(uint8_t digest[DIGEST_MAX_LENGTH],
int fd,
const char* path)
{
struct checklist* entry = NULL;
if ( cache && (entry = checklist_lookup(cache, cache_used, path)) )
{
struct stat st;
fstat(fd, &st);
if ( st.st_mtim.tv_sec < cache_time.tv_sec ||
(st.st_mtim.tv_sec == cache_time.tv_sec &&
st.st_mtim.tv_nsec <= cache_time.tv_nsec) )
{
memcpy(digest, entry->checksum, hash->digest_size);
return 0;
}
}
union ctx ctx;
hash->init(&ctx);
ssize_t amount;
@ -140,6 +317,22 @@ static int digest_fd(uint8_t digest[DIGEST_MAX_LENGTH],
return 1;
}
hash->final(digest, &ctx);
if ( cache )
{
if ( entry )
memcpy(entry->checksum, digest, hash->digest_size);
else
{
checklist_add(&cache, &cache_used, &cache_length, digest, path);
size_t i = cache_used - 1;
while ( i && 0 < strcmp(cache[i - 1]->file, cache[i]->file) )
{
struct checklist* t = cache[i - 1];
cache[i - 1] = cache[i];
cache[i--] = t;
}
}
}
return 0;
}
@ -175,27 +368,6 @@ static int verify_path(uint8_t checksum[], const char* path)
return status;
}
struct checklist
{
const char* file;
uint8_t checksum[DIGEST_MAX_LENGTH];
bool initialized;
};
static int compare_checklist_file(const void* a_ptr, const void* b_ptr)
{
struct checklist* a = *(struct checklist**) a_ptr;
struct checklist* b = *(struct checklist**) b_ptr;
return strcmp(a->file, b->file);
}
static int search_checklist_file(const void* file_ptr, const void* elem_ptr)
{
const char* file = (const char*) file_ptr;
struct checklist* elem = *(struct checklist**) elem_ptr;
return strcmp(file, elem->file);
}
static int checklist_fp(FILE* fp,
const char* path,
size_t files_count,
@ -217,7 +389,6 @@ static int checklist_fp(FILE* fp,
qsort(checklist_sorted, files_count, sizeof(struct checklist*),
compare_checklist_file);
}
uint8_t checksum[DIGEST_MAX_LENGTH];
bool any = false;
char* line = NULL;
size_t line_size = 0;
@ -225,52 +396,29 @@ static int checklist_fp(FILE* fp,
off_t line_number = 0;
size_t read_failures = 0;
size_t check_failures = 0;
struct checklist input;
while ( 0 < (line_length = getline(&line, &line_size, fp)) )
{
line_number++;
if ( line[line_length - 1] != '\n' )
errx(1, "%s:%ji: Line was not terminated with a newline",
path, (intmax_t) line_number);
line[--line_length] = '\0';
if ( (size_t) line_length < 2 * hash->digest_size )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
for ( size_t i = 0; i < hash->digest_size; i++ )
{
int higher = debase(line[i*2 + 0]);
int lower = debase(line[i*2 + 1]);
if ( higher == -1 || lower == -1 )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
checksum[i] = higher << 4 | lower;
}
if ( line[2 * hash->digest_size + 0] != ' ' ||
line[2 * hash->digest_size + 1] != ' ' ||
line[2 * hash->digest_size + 2] == '\0' )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
const char* file = line + 2 * hash->digest_size + 2;
if ( !strcmp(path, "-") && !strcmp(file, "-") )
errx(1, "%s:%ji: Improperly formatted %s checksum line",
path, (intmax_t) line_number, hash->name);
checklist_parse(line, (size_t) line_length, &input, path, line_number);
if ( files )
{
struct checklist** entry_ptr =
bsearch(file, checklist_sorted, files_count,
bsearch(input.file, checklist_sorted, files_count,
sizeof(struct checksum*), search_checklist_file);
if ( entry_ptr )
{
struct checklist* entry = *entry_ptr;
if ( entry->initialized )
errx(1, "%s:%ji: Duplicate hash found for: %s", path,
(intmax_t) line_number, file);
memcpy(entry->checksum, checksum, DIGEST_MAX_LENGTH);
(intmax_t) line_number, input.file);
memcpy(entry->checksum, input.checksum, DIGEST_MAX_LENGTH);
entry->initialized = true;
}
}
else
{
int status = verify_path(checksum, file);
int status = verify_path(input.checksum, input.file);
if ( status == 1 )
read_failures++;
else if ( status == 2 )
@ -296,7 +444,7 @@ static int checklist_fp(FILE* fp,
else if ( status == 2 )
check_failures++;
}
explicit_bzero(checksum, sizeof(checksum));
explicit_bzero(input.checksum, sizeof(input.checksum));
free(checklist);
free(checklist_sorted);
if ( read_failures )
@ -391,6 +539,15 @@ int main(int argc, char* argv[])
}
else if ( !strncmp(arg, "--algorithm=", strlen("--algorithm=")) )
algorithm = arg + strlen("--algorithm=");
else if ( !strcmp(arg, "--cache") )
{
if ( i + 1 == argc )
errx(1, "option '--cache' requires an argument");
cache_path = argv[i+1];
argv[++i] = NULL;
}
else if ( !strncmp(arg, "--cache=", strlen("--cache=")) )
cache_path = arg + strlen("--cache=");
else if ( !strcmp(arg, "--check") )
check = true;
else if ( !strcmp(arg, "--checklist") )
@ -439,6 +596,14 @@ int main(int argc, char* argv[])
else
errx(1, "No hash algorithm was specified with -a");
if ( cache_path )
{
if ( !strcmp(cache_path, "-") )
errx(1, "cache cannot be the standard input");
checklist_read(&cache, &cache_used, &cache_length, &cache_time,
cache_path, true);
}
bool read_failures = false;
bool check_failures = false;
@ -464,10 +629,10 @@ int main(int argc, char* argv[])
else
{
uint8_t digest[DIGEST_MAX_LENGTH];
int result = digest_fd(digest, 0, "-");
int result = digest_path(digest, "-");
if ( result == 0 )
{
printhex(digest, hash->digest_size);
fprinthex(stdout, digest, hash->digest_size);
puts(" -");
explicit_bzero(digest, sizeof(digest));
}
@ -491,7 +656,7 @@ int main(int argc, char* argv[])
int result = digest_path(digest, argv[i]);
if ( result == 0 )
{
printhex(digest, hash->digest_size);
fprinthex(stdout, digest, hash->digest_size);
printf(" %s\n", argv[i]);
explicit_bzero(digest, sizeof(digest));
}
@ -502,5 +667,9 @@ int main(int argc, char* argv[])
if ( ferror(stdout) || fflush(stdout) == EOF )
return 1;
if ( cache_path)
checklist_write(cache, cache_used, cache_path);
return read_failures ? 1 : check_failures ? 2 : 0;
}

14
checksum/compat/sha2.h Normal file
View file

@ -0,0 +1,14 @@
#ifndef SHA2_H
#define SHA2_H
#if __has_include_next(<sha2.h>)
#include_next <sha2.h>
#else
#include "../../libc/include/sha2.h"
#include "../../libc/sha2/sha224.c"
#include "../../libc/sha2/sha256.c"
#include "../../libc/sha2/sha384.c"
#include "../../libc/sha2/sha512_256.c"
#include "../../libc/sha2/sha512.c"
#define timingsafe_memcmp memcmp
#endif
#endif

View file

@ -49,6 +49,7 @@
#include <mount/blockdevice.h>
#include <mount/devices.h>
#include <mount/ext2.h>
#include <mount/fat.h>
#include <mount/filesystem.h>
#include <mount/gpt.h>
#include <mount/harddisk.h>
@ -1496,7 +1497,12 @@ static char* display_area_format(void* ctx, size_t row, size_t column)
{
case FILESYSTEM_ERROR_NONE: return strdup("(no error)");
case FILESYSTEM_ERROR_ABSENT: return strdup("none");
case FILESYSTEM_ERROR_UNRECOGNIZED: return strdup("unrecognized");
case FILESYSTEM_ERROR_UNRECOGNIZED:
{
char uuid[UUID_STRING_LENGTH + 1];
uuid_to_string(area->p->gpt_type_guid, uuid);
return strdup(uuid);
}
case FILESYSTEM_ERROR_ERRNO: return strdup("(error)");
}
return strdup("(unknown error)");
@ -1771,9 +1777,9 @@ static void on_mkpart(size_t argc, char** argv)
bool is_gpt = current_pt_type == PARTITION_TABLE_TYPE_GPT;
const char* question = "Format a filesystem? (no/ext2)";
if ( is_mbr )
question = "Format a filesystem? (no/ext2/extended)";
question = "Format a filesystem? (no/ext2/extended/fat)";
else if ( is_gpt )
question = "Format a filesystem? (no/ext2/biosboot)";
question = "Format a filesystem? (no/ext2/biosboot/efi/fat)";
if ( 5 <= argc )
strlcpy(fstype, argv[4], sizeof(fstype));
else
@ -1781,7 +1787,9 @@ static void on_mkpart(size_t argc, char** argv)
if ( strcmp(fstype, "no") != 0 &&
strcmp(fstype, "ext2") != 0 &&
(!is_mbr || strcmp(fstype, "extended") != 0) &&
(!is_gpt || strcmp(fstype, "biosboot") != 0) )
(!is_gpt || strcmp(fstype, "biosboot") != 0) &&
(!is_gpt || strcmp(fstype, "efi") != 0) &&
strcmp(fstype, "fat") != 0 )
{
command_errorx("%s: %s: Invalid filesystem choice: %s",
argv[0], device_name(current_hd->path), fstype);
@ -1801,14 +1809,18 @@ static void on_mkpart(size_t argc, char** argv)
break;
}
char mountpoint[256] = "";
bool mountable = !strcmp(fstype, "ext2");
// TODO: Get this information from libmount.
bool mountable = !strcmp(fstype, "ext2") ||
!strcmp(fstype, "fat") ||
!strcmp(fstype, "efi");
while ( mountable )
{
const char* def = !strcmp(fstype, "efi") ? "/boot/efi" : "no";
if ( 6 <= argc )
strlcpy(mountpoint, argv[5], sizeof(mountpoint));
else
prompt(mountpoint, sizeof(mountpoint),
"Where to mount partition? (mountpoint or 'no')", "no");
"Where to mount partition? (mountpoint or 'no')", def);
if ( !strcmp(mountpoint, "no") )
{
mountpoint[0] = '\0';
@ -2003,6 +2015,10 @@ static void on_mkpart(size_t argc, char** argv)
const char* type_uuid_str = "0FC63DAF-8483-4772-8E79-3D69D8477DE4";
if ( !strcmp(fstype, "biosboot") )
type_uuid_str = BIOSBOOT_GPT_TYPE_UUID;
else if ( !strcmp(fstype, "efi") )
type_uuid_str = ESP_GPT_TYPE_UUID;
else if ( !strcmp(fstype, "fat") )
type_uuid_str = BDP_GPT_TYPE_UUID;
uuid_from_string(p.partition_type_guid, type_uuid_str);
arc4random_buf(p.unique_partition_guid, sizeof(p.unique_partition_guid));
off_t pstart = hole->start + start;
@ -2162,6 +2178,68 @@ static void on_mkpart(size_t argc, char** argv)
}
}
}
if ( !strcmp(fstype, "efi") || !strcmp(fstype, "fat") )
{
printf("(Formatting %s as %s...)\n", device_name(p->path), fstype);
// TODO: Zero superblock?
// TODO: Run this in its own foreground process group so ^C works.
pid_t child_pid = fork();
if ( child_pid < 0 )
{
command_error("%s: fork", argv[0]);
return;
}
const char* mkfs_argv[] =
{
"mkfs.fat",
"-F", // TODO: Force FAT32 until FAT12/16 root dir writing is added.
"32",
p->path,
NULL
};
if ( child_pid == 0 )
{
execvp(mkfs_argv[0], (char* const*) mkfs_argv);
warn("%s", mkfs_argv[0]);
_exit(127);
}
int status;
waitpid(child_pid, &status, 0);
if ( WIFEXITED(status) && WEXITSTATUS(status) == 127 )
{
command_errorx("%s: Failed to format filesystem (%s is not installed)",
argv[0], mkfs_argv[0]);
return;
}
else if ( WIFEXITED(status) && WEXITSTATUS(status) != 0 )
{
command_errorx("%s: Failed to format filesystem", argv[0]);
return;
}
else if ( WIFSIGNALED(status) )
{
command_errorx("%s: Failed to format filesystem (%s)",
argv[0], strsignal(WTERMSIG(status)));
return;
}
printf("(Formatted %s as %s)\n", device_name(p->path), fstype);
scan_partition(p);
if ( !p->bdev.fs /* TODO: || !(p->bdev.fs->flags & FILESYSTEM_FLAG_UUID) */ )
{
command_errorx("%s: %s: Failed to scan expected %s filesystem",
argv[0], device_name(p->path), fstype);
return;
}
if ( mountpoint[0] )
{
if ( !add_blockdevice_to_fstab(&p->bdev, mountpoint) )
{
command_error("%s: %s: Failed to add partition", argv[0], fstab_path);
return;
}
}
}
}
static void on_mktable(size_t argc, char** argv)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2016, 2023, 2024 Jonas 'Sortie' Termansen.
* Copyright (c) 2023 Juhani 'nortti' Krekelä.
*
* Permission to use, copy, modify, and distribute this software for any
@ -149,15 +149,15 @@ CONNECTION_MESSAGE_HANDLER_NO_AUX(shutdown)
{
(void) connection;
if ( msg->code == 0 )
exit(0);
display_exit(server->display, 0);
else if ( msg->code == 1 )
exit(1);
display_exit(server->display, 1);
else if ( msg->code == 2 )
exit(2);
display_exit(server->display, 2);
else if ( msg->code == 3 )
exit(3);
display_exit(server->display, 3);
else
exit(0);
display_exit(server->display, 0);
}
CONNECTION_MESSAGE_HANDLER_NO_AUX(create_window)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2016, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -25,6 +25,7 @@
#include <display-protocol.h>
#include "display.h"
#include "server.h"
#include "window.h"

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2016, 2018, 2022, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014-2016, 2018, 2022-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -23,6 +23,8 @@
#include <assert.h>
#include <err.h>
#include <inttypes.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@ -47,6 +49,43 @@ void display_initialize(struct display* display)
{
memset(display, 0, sizeof(*display));
display->redraw = true;
display->running = true;
}
static int get_init_exit_plan(void)
{
FILE* fp = popen("/sbin/service default exit-code", "r");
if ( !fp )
return -1;
int result = -1;
char buffer[sizeof(int) * 3];
if ( fgets(buffer, sizeof(buffer), fp) && buffer[0] )
result = atoi(buffer);
pclose(fp);
return result;
}
void display_exit(struct display* display, int exit_code)
{
display->running = false;
display->exit_code = exit_code;
int plan = exit_code == -1 ? get_init_exit_plan() : exit_code;
display->announcement = "Exiting...";
if ( plan == 0 )
display->announcement = "Powering off...";
else if ( plan == 1 )
display->announcement = "Rebooting...";
else if ( plan == 2 )
display->announcement = "Halting...";
else if ( plan == 3 )
display->announcement = "Reinitializing operating system...";
else if ( getenv("LOGIN_PID") ) // TODO: display -l?
{
intmax_t login_pid = strtoimax(getenv("LOGIN_PID"), NULL, 10);
if ( login_pid == getppid() )
display->announcement = "Logging out...";
}
display->redraw = true;
}
void assert_is_well_formed_display_list(struct display* display)
@ -281,6 +320,31 @@ static void wallpaper(struct framebuffer fb)
}
}
static void display_render_exit(struct display* display, struct framebuffer fb)
{
for ( int yoff = -1; yoff <= 1; yoff++ )
{
for ( int xoff = -1; xoff <= 1; xoff++ )
{
struct framebuffer msgfb = fb;
int y = (fb.yres - FONT_HEIGHT) / 2 + yoff;
msgfb = framebuffer_cut_top_y(msgfb, y);
int w = strlen(display->announcement) * (FONT_WIDTH+1);
int x = (fb.xres - w) / 2 + xoff;
msgfb = framebuffer_cut_left_x(msgfb, x);
render_text(msgfb, display->announcement, make_color_a(0, 0, 0, 64));
}
}
struct framebuffer msgfb = fb;
int y = (fb.yres - FONT_HEIGHT) / 2;
msgfb = framebuffer_cut_top_y(msgfb, y);
int w = strlen(display->announcement) * (FONT_WIDTH+1);
int x = (fb.xres - w) / 2;
msgfb = framebuffer_cut_left_x(msgfb, x);
render_text(msgfb, display->announcement, make_color(255, 255, 255));
}
void display_composit(struct display* display, struct framebuffer fb)
{
struct damage_rect damage_rect = display->damage_rect;
@ -301,6 +365,12 @@ void display_composit(struct display* display, struct framebuffer fb)
framebuffer_copy_to_framebuffer(fb, display->wallpaper);
if ( display->announcement )
{
display_render_exit(display, fb);
return;
}
for ( struct window* window = display->bottom_window;
window;
window = window->above_window )
@ -482,11 +552,15 @@ void display_keyboard_event(struct display* display, uint32_t codepoint)
case KBKEY_RSUPER: display->key_rsuper = kbkey > 0; break;
}
if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_DELETE )
exit(0);
display_exit(display, -1);
if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_T )
{
if ( !fork() )
{
sigset_t sigterm;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
sigprocmask(SIG_UNBLOCK, &sigterm, NULL);
execlp("terminal", "terminal", (char*) NULL);
_exit(127);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2016, 2017, 2022, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014-2017, 2022-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -119,5 +119,5 @@ int main(int argc, char* argv[])
server_mainloop(&server);
return 0;
return display.exit_code;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2016, 2022, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2016, 2022, 2023, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -68,14 +68,19 @@ struct display
bool key_rsuper;
bool codepoint_discard;
bool redraw;
bool running;
int pointer_x;
int pointer_y;
enum mouse_state mouse_state;
size_t mouse_byte_count;
uint8_t mouse_bytes[MOUSE_PACKET_SIZE];
const char* announcement;
int exit_code;
};
void display_initialize(struct display* display);
void display_exit(struct display* display, int exit_code);
void assert_is_well_formed_display_list(struct display* display);
void assert_is_well_formed_display(struct display* display);
void display_link_window_at_top(struct display* display, struct window* window);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2014, 2015, 2016, 2023, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -28,6 +28,7 @@
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
@ -40,6 +41,14 @@
#include "server.h"
#include "vgafont.h"
static volatile sig_atomic_t got_sigterm;
static void on_sigterm(int signum)
{
(void) signum;
got_sigterm = 1;
}
static int open_local_server_socket(const char* path, int flags)
{
size_t path_length = strlen(path);
@ -196,9 +205,24 @@ void server_poll(struct server* server)
}
size_t pfds_used = cpfd_off + connections_polled;
int num_events = ppoll(pfds, pfds_used, NULL, NULL);
sigset_t mask;
sigprocmask(SIG_SETMASK, NULL, &mask);
sigdelset(&mask, SIGTERM);
int num_events = ppoll(pfds, pfds_used, NULL, &mask);
if ( got_sigterm )
{
display_exit(server->display, -1);
got_sigterm = 0;
}
if ( num_events < 0 )
{
if ( errno == EINTR )
return;
err(1, "poll");
}
if ( pfds[0].revents )
{
@ -273,9 +297,20 @@ void server_poll(struct server* server)
void server_mainloop(struct server* server)
{
while ( true )
sigset_t sigterm, oldset;
sigemptyset(&sigterm);
sigaddset(&sigterm, SIGTERM);
sigprocmask(SIG_BLOCK, &sigterm, &oldset);
struct sigaction sa = { .sa_handler = on_sigterm }, old_sa;
sigaction(SIGTERM, &sa, &old_sa);
while ( server->display->running )
{
display_render(server->display);
server_poll(server);
}
display_render(server->display);
sigaction(SIGTERM, &old_sa, NULL);
sigprocmask(SIG_SETMASK, &oldset, NULL);
}

View file

@ -25,6 +25,7 @@
#include <limits.h>
#include <locale.h>
#include <pwd.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
@ -270,6 +271,14 @@ bool editor_save_file(struct editor* editor, const char* path)
return fclose(fp) != EOF;
}
volatile sig_atomic_t had_sigwinch = 0;
static void sigwinch(int signum)
{
(void) signum;
had_sigwinch = 1;
}
int main(int argc, char* argv[])
{
setlocale(LC_ALL, "");
@ -279,6 +288,13 @@ int main(int argc, char* argv[])
if ( !isatty(1) )
err(1, "standard output");
sigset_t sigwinch_set;
sigemptyset(&sigwinch_set);
sigaddset(&sigwinch_set, SIGWINCH);
sigprocmask(SIG_BLOCK, &sigwinch_set, NULL);
struct sigaction sa = { .sa_handler = sigwinch };
sigaction(SIGWINCH, &sa, NULL);
struct editor editor;
initialize_editor(&editor);

View file

@ -353,12 +353,22 @@ void editor_input_process_byte(struct editor_input* editor_input,
}
}
extern volatile sig_atomic_t had_sigwinch;
void editor_input_process(struct editor_input* editor_input,
struct editor* editor)
{
sigset_t sigset;
sigemptyset(&sigset);
struct pollfd pfd = { .fd = 0, .events = POLLIN };
do editor_input_process_byte(editor_input, editor);
while ( poll(&pfd, 1, 0) == 1 );
struct timespec timeout = { .tv_sec = 0 };
struct timespec* timeout_ptr = NULL;
while ( !had_sigwinch && ppoll(&pfd, 1, timeout_ptr, &sigset) == 1 )
{
editor_input_process_byte(editor_input, editor);
timeout_ptr = &timeout;
}
had_sigwinch = 0;
}
void editor_input_end(struct editor_input* editor_input)

View file

@ -0,0 +1,2 @@
untrusted comment: signify public key
RWQiTQbFzyZJVobf/pn53Jp3njhRB9DgwkMaNakCpDE9RaTABMjlbz9W

View file

@ -0,0 +1,2 @@
untrusted comment: signify public key
RWQnkSm9lj1YIZYpt1Y3mHYzFsaky82gQF6CrW4lme9OoEYzSIl2ZsIC

View file

@ -0,0 +1,2 @@
untrusted comment: signify public key
RWTGrBXmGvl2zUpCa47ui5EyPsnitKLjsCZ2YZphNY8F3b33t6QWYDs1

View file

@ -0,0 +1,2 @@
untrusted comment: signify public key
RWRTbLQ+3+a9I5yche2BEVP03TRtumGO4Vgq1AQ/5bRj8JAJ1R0+vpxE

2
fat/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
fatfs
*.o

34
fat/Makefile Normal file
View file

@ -0,0 +1,34 @@
include ../build-aux/platform.mak
include ../build-aux/compiler.mak
include ../build-aux/version.mak
include ../build-aux/dirs.mak
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
CXXFLAGS?=$(OPTLEVEL)
CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti -fcheck-new
LIBS:=$(LIBS)
ifeq ($(HOST_IS_SORTIX),0)
PTHREAD_OPTION:=-pthread
LIBS:=$(LIBS) -lfuse
CPPFLAGS:=$(CPPFLAGS) -D_FILE_OFFSET_BITS=64
endif
BINARIES:=fatfs
all: $(BINARIES)
.PHONY: all install clean
install: all
mkdir -p $(DESTDIR)$(SBINDIR)
install $(BINARIES) $(DESTDIR)$(SBINDIR)
fatfs: *.cpp *.h
$(CXX) $(PTHREAD_OPTION) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) *.cpp -o $@ $(LIBS)
clean:
rm -f $(BINARIES) *.o

165
fat/block.cpp Normal file
View file

@ -0,0 +1,165 @@
/*
* Copyright (c) 2013, 2014, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* block.cpp
* Blocks in the filesystem.
*/
#include <sys/types.h>
#include <assert.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include "block.h"
#include "device.h"
#include "ioleast.h"
Block::Block()
{
this->block_data = NULL;
}
Block::Block(Device* device, uint32_t block_id)
{
Construct(device, block_id);
}
void Block::Construct(Device* device, uint32_t block_id)
{
this->modify_lock = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
this->transit_done_cond = PTHREAD_COND_INITIALIZER;
this->prev_block = NULL;
this->next_block = NULL;
this->prev_hashed = NULL;
this->next_hashed = NULL;
this->prev_dirty = NULL;
this->next_dirty = NULL;
this->device = device;
this->reference_count = 1;
this->block_id = block_id;
this->dirty = false;
this->is_in_transit = false;
}
Block::~Block()
{
Destruct();
delete[] block_data;
}
void Block::Destruct()
{
Sync();
Unlink();
}
void Block::Refer()
{
reference_count++;
}
void Block::Unref()
{
if ( !--reference_count )
{
#if 0
device->block_count--;
delete this;
#endif
}
}
void Block::Sync()
{
if ( device->has_sync_thread )
{
pthread_mutex_lock(&device->sync_thread_lock);
while ( dirty || is_in_transit )
pthread_cond_wait(&transit_done_cond, &device->sync_thread_lock);
pthread_mutex_unlock(&device->sync_thread_lock);
return;
}
if ( !dirty )
return;
dirty = false;
(prev_dirty ? prev_dirty->next_dirty : device->dirty_block) = next_dirty;
if ( next_dirty )
next_dirty->prev_dirty = prev_dirty;
prev_dirty = NULL;
next_dirty = NULL;
if ( !device->write )
return;
off_t file_offset = (off_t) device->block_size * (off_t) block_id;
pwriteall(device->fd, block_data, device->block_size, file_offset);
}
void Block::BeginWrite()
{
assert(device->write);
pthread_mutex_lock(&modify_lock);
}
void Block::FinishWrite()
{
pthread_mutex_unlock(&modify_lock);
pthread_mutex_lock(&device->sync_thread_lock);
if ( !dirty )
{
dirty = true;
prev_dirty = NULL;
next_dirty = device->dirty_block;
if ( next_dirty )
next_dirty->prev_dirty = this;
device->dirty_block = this;
pthread_cond_signal(&device->sync_thread_cond);
}
pthread_mutex_unlock(&device->sync_thread_lock);
Use();
}
void Block::Use()
{
Unlink();
Prelink();
}
void Block::Unlink()
{
(prev_block ? prev_block->next_block : device->mru_block) = next_block;
(next_block ? next_block->prev_block : device->lru_block) = prev_block;
size_t bin = block_id % DEVICE_HASH_LENGTH;
(prev_hashed ? prev_hashed->next_hashed : device->hash_blocks[bin]) = next_hashed;
if ( next_hashed ) next_hashed->prev_hashed = prev_hashed;
}
void Block::Prelink()
{
prev_block = NULL;
next_block = device->mru_block;
if ( device->mru_block )
device->mru_block->prev_block = this;
device->mru_block = this;
if ( !device->lru_block )
device->lru_block = this;
size_t bin = block_id % DEVICE_HASH_LENGTH;
prev_hashed = NULL;
next_hashed = device->hash_blocks[bin];
device->hash_blocks[bin] = this;
if ( next_hashed )
next_hashed->prev_hashed = this;
}

62
fat/block.h Normal file
View file

@ -0,0 +1,62 @@
/*
* Copyright (c) 2013, 2014, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* block.h
* Blocks in the filesystem.
*/
#ifndef BLOCK_H
#define BLOCK_H
class Device;
class Block
{
public:
Block();
Block(Device* device, uint32_t block_id);
~Block();
void Construct(Device* device, uint32_t block_id);
void Destruct();
public:
pthread_mutex_t modify_lock;
pthread_cond_t transit_done_cond;
Block* prev_block;
Block* next_block;
Block* prev_hashed;
Block* next_hashed;
Block* prev_dirty;
Block* next_dirty;
Device* device;
size_t reference_count;
uint32_t block_id;
bool dirty;
bool is_in_transit;
uint8_t* block_data;
public:
void Refer();
void Unref();
void Sync();
void BeginWrite();
void FinishWrite();
void Use();
void Unlink();
void Prelink();
};
#endif

220
fat/device.cpp Normal file
View file

@ -0,0 +1,220 @@
/*
* Copyright (c) 2013, 2014, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* device.cpp
* Block device.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "block.h"
#include "device.h"
#include "ioleast.h"
void* Device__SyncThread(void* ctx)
{
((Device*) ctx)->SyncThread();
return NULL;
}
Device::Device(int fd, const char* path, uint32_t block_size, bool write)
{
// sync_thread unset.
this->sync_thread_cond = PTHREAD_COND_INITIALIZER;
this->sync_thread_idle_cond = PTHREAD_COND_INITIALIZER;
this->sync_thread_lock = PTHREAD_MUTEX_INITIALIZER;
this->mru_block = NULL;
this->lru_block = NULL;
this->dirty_block = NULL;
for ( size_t i = 0; i < DEVICE_HASH_LENGTH; i++ )
hash_blocks[i] = NULL;
struct stat st;
fstat(fd, &st);
this->device_size = st.st_size;
this->path = path;
this->block_size = block_size;
this->fd = fd;
this->write = write;
this->has_sync_thread = false;
this->sync_thread_should_exit = false;
this->sync_in_transit = false;
this->block_count = 0;
#ifdef __sortix__
// TODO: This isn't scaleable if there's multiple filesystems mounted.
size_t memory;
memstat(NULL, &memory);
this->block_limit = (memory / 10) / block_size;
#else
this->block_limit = 32768;
#endif
}
Device::~Device()
{
if ( has_sync_thread )
{
pthread_mutex_lock(&sync_thread_lock);
sync_thread_should_exit = true;
pthread_cond_signal(&sync_thread_cond);
pthread_mutex_unlock(&sync_thread_lock);
pthread_join(sync_thread, NULL);
has_sync_thread = false;
}
Sync();
while ( mru_block )
delete mru_block;
close(fd);
}
void Device::SpawnSyncThread()
{
if ( this->has_sync_thread )
return;
this->has_sync_thread = write &&
pthread_create(&this->sync_thread, NULL, Device__SyncThread, this) == 0;
}
Block* Device::AllocateBlock()
{
if ( block_limit <= block_count )
{
for ( Block* block = lru_block; block; block = block->prev_block )
{
if ( block->reference_count )
continue;
block->Destruct(); // Syncs.
return block;
}
}
uint8_t* data = new uint8_t[block_size];
if ( !data ) // TODO: Use operator new nothrow!
return NULL;
Block* block = new Block();
if ( !block ) // TODO: Use operator new nothrow!
return delete[] data, (Block*) NULL;
block->block_data = data;
block_count++;
return block;
}
Block* Device::GetBlock(uint32_t block_id)
{
if ( Block* block = GetCachedBlock(block_id) )
return block;
Block* block = AllocateBlock();
if ( !block )
return NULL;
block->Construct(this, block_id);
off_t file_offset = (off_t) block_size * (off_t) block_id;
preadall(fd, block->block_data, block_size, file_offset);
block->Prelink();
return block;
}
Block* Device::GetBlockZeroed(uint32_t block_id)
{
assert(write);
if ( Block* block = GetCachedBlock(block_id) )
{
block->BeginWrite();
memset(block->block_data, 0, block_size);
block->FinishWrite();
return block;
}
Block* block = AllocateBlock();
if ( !block )
return NULL;
block->Construct(this, block_id);
memset(block->block_data, 0, block_size);
block->Prelink();
block->BeginWrite();
block->FinishWrite();
return block;
}
Block* Device::GetCachedBlock(uint32_t block_id)
{
size_t bin = block_id % DEVICE_HASH_LENGTH;
for ( Block* iter = hash_blocks[bin]; iter; iter = iter->next_hashed )
if ( iter->block_id == block_id )
return iter->Refer(), iter;
return NULL;
}
void Device::Sync()
{
if ( has_sync_thread )
{
pthread_mutex_lock(&sync_thread_lock);
while ( dirty_block || sync_in_transit )
pthread_cond_wait(&sync_thread_cond, &sync_thread_lock);
pthread_mutex_unlock(&sync_thread_lock);
fsync(fd);
return;
}
while ( dirty_block )
dirty_block->Sync();
fsync(fd);
}
void Device::SyncThread()
{
uint8_t transit_block_data[block_size];
pthread_mutex_lock(&sync_thread_lock);
while ( true )
{
while ( !(dirty_block || sync_thread_should_exit) )
pthread_cond_wait(&sync_thread_cond, &sync_thread_lock);
if ( sync_thread_should_exit )
break;
Block* block = dirty_block;
if ( block->next_dirty )
block->next_dirty->prev_dirty = NULL;
dirty_block = block->next_dirty;
block->next_dirty = NULL;
block->dirty = false;
block->is_in_transit = true;
sync_in_transit = true;
pthread_mutex_unlock(&sync_thread_lock);
pthread_mutex_lock(&block->modify_lock);
memcpy(transit_block_data, block->block_data, block_size);
pthread_mutex_unlock(&block->modify_lock);
off_t offset = (off_t) block_size * (off_t) block->block_id;
pwriteall(fd, transit_block_data, block_size, offset);
pthread_mutex_lock(&sync_thread_lock);
block->is_in_transit = false;
sync_in_transit = false;
pthread_cond_signal(&block->transit_done_cond);
if ( !dirty_block )
pthread_cond_signal(&sync_thread_idle_cond);
}
pthread_mutex_unlock(&sync_thread_lock);
}

64
fat/device.h Normal file
View file

@ -0,0 +1,64 @@
/*
* Copyright (c) 2013, 2014, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* device.h
* Block device.
*/
#ifndef DEVICE_H
#define DEVICE_H
class Block;
static const size_t DEVICE_HASH_LENGTH = 1 << 16;
class Device
{
public:
Device(int fd, const char* path, uint32_t block_size, bool write);
~Device();
public:
pthread_t sync_thread;
pthread_cond_t sync_thread_cond;
pthread_cond_t sync_thread_idle_cond;
pthread_mutex_t sync_thread_lock;
Block* mru_block;
Block* lru_block;
Block* dirty_block;
Block* hash_blocks[DEVICE_HASH_LENGTH];
off_t device_size;
const char* path;
uint32_t block_size;
int fd;
bool write;
bool has_sync_thread;
bool sync_thread_should_exit;
bool sync_in_transit;
size_t block_count;
size_t block_limit;
public:
void SpawnSyncThread();
Block* AllocateBlock();
Block* GetBlock(uint32_t block_id);
Block* GetBlockZeroed(uint32_t block_id);
Block* GetCachedBlock(uint32_t block_id);
void Sync();
void SyncThread();
};
#endif

124
fat/fat.h Normal file
View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fat.h
* The File Allocation Table (FAT) filesystem.
*/
#ifndef FAT_H
#define FAT_H
#include <assert.h>
#include <stdint.h>
// TODO: Dammit. I forgot to swap endian all across the codebase.
struct fat_bpb
{
uint8_t jump[3];
char oem[8];
uint8_t bytes_per_sector_low;
uint8_t bytes_per_sector_high;
uint8_t sectors_per_cluster;
uint16_t reserved_sectors;
uint8_t fat_count;
uint8_t root_dirent_count_low;
uint8_t root_dirent_count_high;
uint8_t total_sectors_low;
uint8_t total_sectors_high;
uint8_t media_descriptor_type;
uint16_t sectors_per_fat;
uint16_t sectors_per_track;
uint16_t head_count;
uint16_t hidden_sectors;
uint32_t total_sectors_large;
union
{
struct
{
uint8_t fat12_drive_number;
uint8_t fat12_reserved;
uint8_t fat12_signature;
uint8_t fat12_volume_id[4];
uint8_t fat12_volume_label[11];
uint8_t fat12_system[8];
};
struct
{
uint32_t fat32_sectors_per_fat;
uint16_t fat32_flags;
uint16_t fat32_version;
uint32_t fat32_root_cluster;
uint16_t fat32_fsinfo;
uint16_t fat32_backup_boot;
uint32_t fat32_reserved1[3];
uint8_t fat32_drive_number;
uint8_t fat32_reserved2;
uint8_t fat32_signature;
uint8_t fat32_volume_id[4];
uint8_t fat32_volume_label[11];
uint8_t fat32_system[8];
};
struct
{
uint8_t bootloader[510 - 36];
uint8_t boot_signature[2];
};
};
};
static_assert(sizeof(struct fat_bpb) == 512, "sizeof(struct fat_bpb) == 512");
struct fat_fsinfo
{
uint32_t signature1;
uint32_t reserved1[120];
uint32_t signature2;
uint32_t free_count;
uint32_t next_free;
uint32_t reserved2[3];
uint32_t signature3;
};
static_assert(sizeof(struct fat_fsinfo) == 512, "sizeof(struct fat_fsinfo) == 512");
struct fat_dirent
{
char name[11];
uint8_t attributes;
uint8_t reserved;
uint8_t creation_tenths; // TODO: Misnamed semantically.
uint16_t creation_time;
uint16_t creation_date;
uint16_t access_date;
uint16_t cluster_high;
uint16_t modified_time;
uint16_t modified_date;
uint16_t cluster_low;
uint32_t size;
};
static_assert(sizeof(struct fat_dirent) == 32, "sizeof(struct fat_dirent) == 32");
#define FAT_ATTRIBUTE_READ_ONLY (1 << 0)
#define FAT_ATTRIBUTE_HIDDEN (1 << 1)
#define FAT_ATTRIBUTE_SYSTEM (1 << 2)
#define FAT_ATTRIBUTE_VOLUME_ID (1 << 3)
#define FAT_ATTRIBUTE_DIRECTORY (1 << 4)
#define FAT_ATTRIBUTE_ARCHIVE (1 << 5)
#define FAT_ATTRIBUTE_LONG_NAME 0x0F
#endif

275
fat/fatfs.cpp Normal file
View file

@ -0,0 +1,275 @@
/*
* Copyright (c) 2013, 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fatfs.cpp
* The File Allocation Table (FAT) filesystem.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <dirent.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#if defined(__sortix__)
#include "fsmarshall.h"
#else
#include "fuse.h"
#endif
#include "block.h"
#include "device.h"
#include "fat.h"
#include "fatfs.h"
#include "filesystem.h"
#include "inode.h"
#include "ioleast.h"
#include "util.h"
uid_t request_uid;
uid_t request_gid;
// TODO: Encapsulate.
void StatInode(Inode* inode, struct stat* st)
{
memset(st, 0, sizeof(*st));
st->st_ino = inode->inode_id;
st->st_mode = inode->Mode();
st->st_nlink = 1; // TODO: Encapsulate.
st->st_uid = inode->UserId();
st->st_gid = inode->GroupId();
st->st_size = inode->Size();
// TODO: Encapsulate.
// TODO: For the root dir, mount time, or maybe volume label or first file?
// Or maybe the time of the mount point?
time_t mtime = 0;
if ( inode->dirent )
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_sec = ((inode->dirent->modified_time >> 0) & 0x1F) * 2;
tm.tm_min = (inode->dirent->modified_time >> 5) & 0x3F;
tm.tm_hour = (inode->dirent->modified_time >> 11) & 0x1F;
tm.tm_mday = (inode->dirent->modified_date >> 0) & 0x1F;
tm.tm_mon = ((inode->dirent->modified_date >> 5) & 0xF) - 1;
tm.tm_year = ((inode->dirent->modified_date >> 9) & 0x7F) + 80;
mtime = mktime(&tm);
}
st->st_atim.tv_sec = mtime; // TODO: The actual accessed time;
st->st_atim.tv_nsec = 0;
st->st_ctim.tv_sec = mtime; // TODO: Probably fine to keep as modified time.
st->st_ctim.tv_nsec = 0;
st->st_mtim.tv_sec = mtime;
st->st_mtim.tv_nsec = 0;
st->st_blksize = inode->filesystem->bytes_per_sector *
inode->filesystem->bpb->sectors_per_cluster;
// TODO: Encapsulate.
st->st_blocks = 0; // TODO inode->data->i_blocks;
}
static void compact_arguments(int* argc, char*** argv)
{
for ( int i = 0; i < *argc; i++ )
{
while ( i < *argc && !(*argv)[i] )
{
for ( int n = i; n < *argc; n++ )
(*argv)[n] = (*argv)[n+1];
(*argc)--;
}
}
}
static void help(FILE* fp, const char* argv0)
{
fprintf(fp, "Usage: %s [OPTION]... DEVICE [MOUNT-POINT]\n", argv0);
}
static void version(FILE* fp, const char* argv0)
{
fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR);
}
int main(int argc, char* argv[])
{
const char* argv0 = argv[0];
const char* pretend_mount_path = NULL;
bool foreground = false;
bool read = false;
bool write = false;
for ( int i = 1; i < argc; i++ )
{
const char* arg = argv[i];
if ( arg[0] != '-' || !arg[1] )
continue;
argv[i] = NULL;
if ( !strcmp(arg, "--") )
break;
if ( arg[1] != '-' )
{
while ( char c = *++arg ) switch ( c )
{
case 'r': read = true; break;
case 'w': write = true; break;
default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
help(stderr, argv0);
exit(1);
}
}
else if ( !strcmp(arg, "--help") )
help(stdout, argv0), exit(0);
else if ( !strcmp(arg, "--version") )
version(stdout, argv0), exit(0);
else if ( !strcmp(arg, "--background") )
foreground = false;
else if ( !strcmp(arg, "--foreground") )
foreground = true;
else if ( !strcmp(arg, "--read") )
read = true;
else if ( !strcmp(arg, "--write") )
write = true;
else if ( !strncmp(arg, "--pretend-mount-path=", strlen("--pretend-mount-path=")) )
pretend_mount_path = arg + strlen("--pretend-mount-path=");
else if ( !strcmp(arg, "--pretend-mount-path") )
{
if ( i+1 == argc )
{
fprintf(stderr, "%s: --pretend-mount-path: Missing operand\n", argv0);
exit(1);
}
pretend_mount_path = argv[++i];
argv[i] = NULL;
}
else
{
fprintf(stderr, "%s: unknown option: %s\n", argv0, arg);
help(stderr, argv0);
exit(1);
}
}
// It doesn't make sense to have a write-only filesystem.
read = read || write;
// Default to read and write filesystem access.
if ( !read && !write )
read = write = true;
if ( argc == 1 )
{
help(stdout, argv0);
exit(0);
}
compact_arguments(&argc, &argv);
const char* device_path = 2 <= argc ? argv[1] : NULL;
const char* mount_path = 3 <= argc ? argv[2] : NULL;
if ( !device_path )
{
help(stderr, argv0);
exit(1);
}
if ( !pretend_mount_path )
pretend_mount_path = mount_path;
int fd = open(device_path, write ? O_RDWR : O_RDONLY);
if ( fd < 0 )
err(1, "%s", device_path);
// Read the bios parameter block from the filesystem so we can verify it.
struct fat_bpb bpb;
if ( preadall(fd, &bpb, sizeof(bpb), 0) != sizeof(bpb) )
{
if ( errno == EEOF )
errx(1, "%s: Isn't a valid FAT filesystem (too short)", device_path);
else
err(1, "read: %s", device_path);
}
// Verify the boot.
if ( !(bpb.boot_signature[0] == 0x55 && bpb.boot_signature[1] == 0xAA) )
errx(1, "%s: Isn't a valid FAT filesystem (no boot signature)", device_path);
// Verify the jump instruction at the start of the boot sector.
if ( !(bpb.jump[0] == 0xEB && bpb.jump[2] == 0x90) &&
!(bpb.jump[0] == 0xE9) )
errx(1, "%s: Isn't a valid FAT filesystem (bad jump)", device_path);
// TODO: Validate all parameters make sense.
uint16_t bytes_per_sector =
bpb.bytes_per_sector_low | bpb.bytes_per_sector_high << 8;
uint16_t root_dirent_count =
bpb.root_dirent_count_low | bpb.root_dirent_count_high << 8;
uint32_t root_dir_sectors =
divup<uint32_t>(root_dirent_count * sizeof(fat_dirent), bytes_per_sector);
uint32_t sectors_per_fat =
bpb.sectors_per_fat ? bpb.sectors_per_fat : bpb.fat32_sectors_per_fat;
uint32_t total_sectors =
bpb.total_sectors_low | bpb.total_sectors_high << 8;
if ( !total_sectors )
total_sectors = bpb.total_sectors_large;
uint32_t data_offset =
bpb.reserved_sectors + bpb.fat_count * sectors_per_fat + root_dir_sectors;
uint32_t data_sectors = total_sectors - data_offset;
uint32_t cluster_count = data_sectors / bpb.sectors_per_cluster;
uint8_t fat_type =
cluster_count < 4085 ? 12 : cluster_count < 65525 ? 16 : 32;
// Verify the filesystem version.
if ( fat_type == 32 && bpb.fat32_version != 0x0000 )
errx(1, "%s: Unsupported filesystem version 0x%04x", device_path,
bpb.fat32_version);
// TODO: On FAT16/32 check FAT entry 1 for the high bits to see if fsck is needed.
// Check whether the filesystem was unmounted cleanly.
//if ( !(fat[1] & FAT_UNMOUNTED) || !(fat[1] & FAT_NO_IO_ERR) )
// warn("warning: %s: Filesystem wasn't unmounted cleanly\n", device_path);
// TODO: The FAT and clusters are not aligned to cluster size so
// we can't use the cluster size here. Perhaps refactor the
// device so we can deal with whole clusters.
Device* dev = new Device(fd, device_path, bytes_per_sector, write);
if ( !dev ) // TODO: Use operator new nothrow!
err(1, "malloc");
Filesystem* fs = new Filesystem(dev, pretend_mount_path);
if ( !fs ) // TODO: Use operator new nothrow!
err(1, "malloc");
if ( !mount_path )
return 0;
#if defined(__sortix__)
return fsmarshall_main(argv0, mount_path, foreground, fs, dev);
#else
return fat_fuse_main(argv0, mount_path, foreground, fs, dev);
#endif
}

33
fat/fatfs.h Normal file
View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fatfs.h
* Implementation of the extended filesystem.
*/
#ifndef EXTFS_H
#define EXTFS_H
extern uid_t request_uid;
extern gid_t request_gid;
class Inode;
mode_t HostModeFromExtMode(uint32_t extmode);
uint32_t ExtModeFromHostMode(mode_t hostmode);
uint8_t HostDTFromExtDT(uint8_t extdt);
void StatInode(Inode* inode, struct stat* st);
#endif

446
fat/filesystem.cpp Normal file
View file

@ -0,0 +1,446 @@
/*
* Copyright (c) 2013, 2014, 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* filesystem.cpp
* Filesystem.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <endian.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include "fat.h"
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "inode.h"
#include "util.h"
// TODO: Be more precise.
static bool is_8_3_char(char c)
{
return ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9');
}
// TODO: Is this fully precise? What about .FOO?
bool is_8_3(const char* name)
{
if ( !name[0] )
return false;
size_t b = 0;
while ( name[b] && is_8_3_char(name[b]) )
b++;
if ( 8 < b )
return false;
if ( !name[b] )
return true;
if ( name[b] != '.' )
return false;
size_t e = 0;
while ( name[b+1+e] && is_8_3_char(name[b+1+e]) )
e++;
if ( 3 < e )
return false;
if ( name[b+1+e] )
return false;
return true;
}
void encode_8_3(const char* decoded, char encoded[8 + 3])
{
size_t i = 0;
for ( size_t o = 0; o < 8 + 3; o++ )
{
char c = ' ';
if ( decoded[i] == '.' && o == 8 )
i++;
if ( decoded[i] && decoded[i] != '.' )
c = decoded[i++];
if ( (unsigned char) c == 0xE5 )
c = 0x05;
encoded[o] = c;
}
}
void decode_8_3(const char encoded[8 + 3], char decoded[8 + 1 + 3 + 1])
{
size_t o = 0;
for ( size_t i = 0; i < 8; i++ )
{
char c = encoded[i];
if ( !c || c == ' ' )
break;
if ( c == 0x05 )
c = (char) 0xE5;
decoded[o++] = c;
}
for ( size_t i = 8; i < 8 + 3; i++ )
{
char c = encoded[i];
if ( !c || c == ' ' )
break;
if ( i == 8 )
decoded[o++] = '.';
if ( c == 0x05 )
c = (char) 0xE5;
decoded[o++] = c;
}
decoded[o] = '\0';
}
uint8_t timespec_to_fat_tenths(struct timespec* ts)
{
// TODO: Work with a struct tm instead.
uint16_t hundreds = ts->tv_nsec / 10000000;
struct tm tm;
gmtime_r(&ts->tv_sec, &tm);
if ( tm.tm_sec & 1 )
hundreds += 100;
return hundreds;
}
uint16_t timespec_to_fat_time(struct timespec* ts)
{
// TODO: Work with a struct tm instead.
struct tm tm;
gmtime_r(&ts->tv_sec, &tm);
return (tm.tm_sec / 2) << 0 | tm.tm_min << 5 | tm.tm_hour << 11;
}
uint16_t timespec_to_fat_date(struct timespec* ts)
{
// TODO: Work with a struct tm instead.
struct tm tm;
gmtime_r(&ts->tv_sec, &tm);
return tm.tm_mday << 0 | (tm.tm_mon + 1) << 5 | (tm.tm_year - 80) << 9;
}
// TODO: Rename tenths to a better name.
void timespec_to_fat(const struct timespec* ts, uint16_t* date, uint16_t* time,
uint8_t* tenths)
{
struct tm tm;
gmtime_r(&ts->tv_sec, &tm);
// TODO: Endian.
*date = tm.tm_mday << 0 | (tm.tm_mon + 1) << 5 | (tm.tm_year - 80) << 9;
*time = (tm.tm_sec / 2) << 0 | tm.tm_min << 5 | tm.tm_hour << 11;
*tenths = ts->tv_nsec / 10000000 + (tm.tm_sec & 1 ? 100 : 0);
}
Filesystem::Filesystem(Device* device, const char* mount_path)
{
this->bpb_block = device->GetBlock(0);
assert(bpb_block); // TODO: This can fail.
this->bpb = (struct fat_bpb*) bpb_block->block_data;
this->device = device;
this->mount_path = mount_path;
this->mode_reg = S_IFREG | 0644;
this->mode_dir = S_IFDIR | 0755;
this->block_size = device->block_size;
this->bytes_per_sector =
bpb->bytes_per_sector_low | bpb->bytes_per_sector_high << 8;
this->root_dirent_count =
bpb->root_dirent_count_low | bpb->root_dirent_count_high << 8;
uint32_t root_dir_sectors =
divup<uint32_t>(root_dirent_count * sizeof(fat_dirent), bytes_per_sector);
this->sectors_per_fat =
bpb->sectors_per_fat ? bpb->sectors_per_fat : bpb->fat32_sectors_per_fat;
this->total_sectors =
bpb->total_sectors_low | bpb->total_sectors_high << 8;
if ( !this->total_sectors )
this->total_sectors = bpb->total_sectors_large;
this->fat_sector = bpb->reserved_sectors;
this->root_sector = fat_sector + bpb->fat_count * sectors_per_fat;
this->data_sector = root_sector + root_dir_sectors;
uint32_t data_sectors = total_sectors - data_sector;
this->cluster_count = data_sectors / bpb->sectors_per_cluster;
this->cluster_size = bpb->sectors_per_cluster * bytes_per_sector;
this->fat_type = cluster_count < 4085 ? 12 : cluster_count < 65525 ? 16 : 32;
// Use cluster 1 as the root inode on FAT12/FAT16 since it's not a valid
// cluster for use in the FAT.
this->root_inode_id = fat_type == 32 ? bpb->fat32_root_cluster : 1;
// TODO: Okay we actually need to compare with their lower bounds.
this->eio_cluster =
fat_type == 12 ? 0xFF7 : fat_type == 16 ? 0xFFF7 : 0xFFFFFF7;
this->eof_cluster =
fat_type == 12 ? 0xFFF : fat_type == 16 ? 0xFFFF : 0xFFFFFFF;
// TODO: Obtain and verify this from the fsinfo.
this->free_search = 0;
this->mru_inode = NULL;
this->lru_inode = NULL;
this->dirty_inode = NULL;
for ( size_t i = 0; i < INODE_HASH_LENGTH; i++ )
this->hash_inodes[i] = NULL;
this->dirty = false;
if ( device->write )
{
BeginWrite();
// TODO: Mark as mounted in fat[1] if FAT16/32.
FinishWrite();
Sync();
}
}
Filesystem::~Filesystem()
{
Sync();
while ( mru_inode )
delete mru_inode;
if ( device->write )
{
BeginWrite();
// TODO: Mark as unounted in fat[1] if FAT16/32.
FinishWrite();
Sync();
}
bpb_block->Unref();
}
void Filesystem::BeginWrite()
{
bpb_block->BeginWrite();
}
void Filesystem::FinishWrite()
{
dirty = true;
bpb_block->FinishWrite();
}
void Filesystem::Sync()
{
// TODO: Replacement concept?
while ( dirty_inode )
dirty_inode->Sync();
if ( dirty )
{
bpb_block->Sync();
dirty = false;
}
device->Sync();
}
Inode* Filesystem::GetInode(uint32_t inode_id, Block* dirent_block,
struct fat_dirent* dirent)
{
#if 0
if ( !inode_id || num_inodes <= inode_id )
return errno = EBADF, (Inode*) NULL;
if ( !inode_id )
return errno = EBADF, (Inode*) NULL;
#endif
size_t bin = inode_id % INODE_HASH_LENGTH;
for ( Inode* iter = hash_inodes[bin]; iter; iter = iter->next_hashed )
if ( iter->inode_id == inode_id )
return iter->Refer(), iter;
if ( inode_id != root_inode_id && !dirent_block )
return errno = EBADF, (Inode*) NULL;
Inode* inode = new Inode(this, inode_id);
if ( !inode )
return (Inode*) NULL;
inode->first_cluster =
inode_id == root_inode_id && fat_type != 32 ? 0 : inode_id;
if ( (inode->data_block = dirent_block) )
inode->data_block->Refer();
inode->dirent = dirent;
inode->Prelink();
return inode;
}
uint32_t Filesystem::AllocateCluster()
{
for ( size_t i = 0; i < cluster_count; i++ )
{
size_t n = 2 + (free_search + i) % cluster_count;
if ( !ReadFAT(n) )
{
free_search = (i + 1) % cluster_count;
if ( fat_type == 32 )
{
Block* block = device->GetBlock(bpb->fat32_fsinfo);
if ( block )
{
struct fat_fsinfo* fsinfo =
(struct fat_fsinfo*) block->block_data;
block->BeginWrite();
uint32_t free_count = le32toh(fsinfo->free_count);
if ( free_count )
free_count--;
fsinfo->free_count = htole32(free_count);
fsinfo->next_free = n;
block->FinishWrite();
block->Unref();
}
}
return n;
}
}
return errno = ENOSPC, 0;
}
void Filesystem::FreeCluster(uint32_t cluster)
{
if ( fat_type != 32 )
return;
Block* block = device->GetBlock(bpb->fat32_fsinfo);
if ( !block )
return;
struct fat_fsinfo* fsinfo = (struct fat_fsinfo*) block->block_data;
block->BeginWrite();
uint32_t free_count = le32toh(fsinfo->free_count);
if ( free_count < cluster_count )
free_count++;
fsinfo->free_count = htole32(free_count);
if ( !fsinfo->free_count || le32toh(fsinfo->next_free) == cluster + 1 )
{
fsinfo->next_free = htole32(cluster);
free_search = cluster - 2;
}
block->FinishWrite();
block->Unref();
}
uint32_t Filesystem::ReadFAT(uint32_t cluster)
{
// TODO: Bounds check.
if ( fat_type == 12 )
{
size_t position = cluster + (cluster / 2);
size_t lba = position / bytes_per_sector;
size_t offset = position % bytes_per_sector;
Block* block = device->GetBlock(fat_sector + lba);
if ( !block )
return eio_cluster;
uint8_t lower = block->block_data[offset];
if ( ++offset == bytes_per_sector )
{
block->Unref();
if ( !(block = device->GetBlock(fat_sector + lba)) )
return eio_cluster;
offset = 0;
}
uint8_t higher = block->block_data[offset];
block->Unref();
uint16_t value = lower | higher << 8;
if ( cluster & 1 )
return value >> 4;
else
return value & 0xFFF;
}
size_t fat_size = fat_type / 8;
size_t position = cluster * fat_size;
size_t lba = position / bytes_per_sector;
size_t entry = (position % bytes_per_sector) / fat_size;
Block* block = device->GetBlock(fat_sector + lba);
if ( !block )
return eio_cluster;
uint32_t result = 0;
if ( fat_type == 16 )
result = ((uint16_t*) block->block_data)[entry];
else if ( fat_type == 32 )
result = ((uint32_t*) block->block_data)[entry] & 0x0FFFFFFF;
block->Unref();
return result;
}
bool Filesystem::WriteFAT(uint32_t cluster, uint32_t value)
{
assert(device->write);
// TODO: Bounds check.
if ( fat_type == 12 )
{
size_t position = cluster + (cluster / 2);
size_t lba = position / bytes_per_sector;
size_t offset = position % bytes_per_sector;
Block* block = device->GetBlock(fat_sector + lba);
if ( !block )
return false;
value = cluster & 1 ? value << 4 : value;
uint16_t mask = cluster & 1 ? 0xFFF0 : 0x0FFF;
block->BeginWrite();
block->block_data[offset] &= ~mask;
block->block_data[offset] |= value;
if ( ++offset == bytes_per_sector )
{
block->FinishWrite();
block->Unref();
if ( !(block = device->GetBlock(fat_sector + lba)) )
return false;
offset = 0;
block->BeginWrite();
}
block->block_data[offset] &= ~(mask >> 8);
block->block_data[offset] |= value >> 8;
block->FinishWrite();
block->Unref();
return true;
}
// TODO: Mirror to the other FATs.
size_t fat_size = fat_type / 8;
size_t position = cluster * fat_size;
size_t lba = position / bytes_per_sector;
size_t entry = (position % bytes_per_sector) / fat_size;
Block* block = device->GetBlock(fat_sector + lba);
if ( !block )
return false;
block->BeginWrite();
if ( fat_type == 16 )
((uint16_t*) block->block_data)[entry] = value;
else if ( fat_type == 32 )
{
uint32_t old = ((uint32_t*) block->block_data)[entry] & 0xF0000000;
((uint32_t*) block->block_data)[entry] = value | old;
}
block->FinishWrite();
block->Unref();
return true;
}
uint32_t Filesystem::CalculateFreeCount()
{
if ( fat_type == 32 )
{
// TODO: Verify the fsinfo.
Block* block = device->GetBlock(bpb->fat32_fsinfo);
if ( !block )
return 0xFFFFFFFF;
const struct fat_fsinfo* fsinfo =
(const struct fat_fsinfo*) block->block_data;
uint32_t result = le32toh(fsinfo->free_count);
block->Unref();
if ( result != 0xFFFFFFFF )
return result;
}
// TODO: Cache these.
size_t count = 0;
for ( size_t i = 0; i < cluster_count; i++ )
if ( !ReadFAT(2 + i) )
count++;
return count;
}

88
fat/filesystem.h Normal file
View file

@ -0,0 +1,88 @@
/*
* Copyright (c) 2013, 2014, 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* filesystem.h
* Filesystem.
*/
#ifndef FILESYSTEM_H
#define FILESYSTEM_H
bool is_8_3(const char* name);
void encode_8_3(const char* decoded, char encoded[8 + 3]);
void decode_8_3(const char encoded[8 + 3], char decoded[8 + 1 + 3 + 1]);
void timespec_to_fat(const struct timespec* ts, uint16_t* date, uint16_t* time,
uint8_t* tenths);
// TODO: Unify into the above.
uint8_t timespec_to_fat_tenths(struct timespec* ts);
uint16_t timespec_to_fat_time(struct timespec* ts);
uint16_t timespec_to_fat_date(struct timespec* ts);
class Device;
class Inode;
static const size_t INODE_HASH_LENGTH = 1 << 16;
class Filesystem
{
public:
Filesystem(Device* device, const char* mount_path);
~Filesystem();
public:
Block* bpb_block;
struct fat_bpb* bpb;
Device* device;
const char* mount_path;
mode_t mode_reg;
mode_t mode_dir;
uid_t uid;
gid_t gid;
uint32_t block_size;
uint16_t bytes_per_sector;
uint16_t root_dirent_count;
uint32_t sectors_per_fat;
uint32_t root_inode_id;
uint32_t total_sectors;
uint32_t fat_sector; // TODO: Rename to lba
uint32_t root_sector; // TODO: Rename to lba
uint32_t data_sector; // TODO: Rename to lba
uint32_t cluster_count;
uint32_t cluster_size;
uint8_t fat_type;
uint32_t eio_cluster;
uint32_t eof_cluster;
uint32_t free_search;
Inode* mru_inode;
Inode* lru_inode;
Inode* dirty_inode;
Inode* hash_inodes[INODE_HASH_LENGTH];
bool dirty;
public:
Inode* GetInode(uint32_t inode_id, Block* dirent_block = NULL,
struct fat_dirent* dirent = NULL);
uint32_t AllocateCluster();
void FreeCluster(uint32_t cluster);
uint32_t ReadFAT(uint32_t cluster);
bool WriteFAT(uint32_t cluster, uint32_t value);
uint32_t CalculateFreeCount();
void BeginWrite();
void FinishWrite();
void Sync();
};
#endif

858
fat/fsmarshall.cpp Normal file
View file

@ -0,0 +1,858 @@
/*
* Copyright (c) 2013, 2014, 2015, 2016, 2022, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fsmarshall.cpp
* Sortix fsmarshall frontend.
*/
#if defined(__sortix__)
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <ioleast.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <timespec.h>
#include <unistd.h>
#include <sortix/dirent.h>
#include <fsmarshall.h>
#include "block.h"
#include "device.h"
#include "fat.h"
#include "fatfs.h"
#include "filesystem.h"
#include "fsmarshall.h"
#include "fuse.h"
#include "inode.h"
bool RespondData(int chl, const void* ptr, size_t count)
{
return writeall(chl, ptr, count) == count;
}
bool RespondHeader(int chl, size_t type, size_t size)
{
struct fsm_msg_header hdr;
hdr.msgtype = type;
hdr.msgsize = size;
return RespondData(chl, &hdr, sizeof(hdr));
}
bool RespondMessage(int chl, size_t type, const void* ptr, size_t count)
{
return RespondHeader(chl, type, count) &&
RespondData(chl, ptr, count);
}
bool RespondError(int chl, int errnum)
{
struct fsm_resp_error body;
body.errnum = errnum;
//fprintf(stderr, "fatfs: sending error %i (%s)\n", errnum, strerror(errnum));
return RespondMessage(chl, FSM_RESP_ERROR, &body, sizeof(body));
}
bool RespondSuccess(int chl)
{
struct fsm_resp_success body;
return RespondMessage(chl, FSM_RESP_SUCCESS, &body, sizeof(body));
}
bool RespondStat(int chl, struct stat* st)
{
struct fsm_resp_stat body;
body.st = *st;
return RespondMessage(chl, FSM_RESP_STAT, &body, sizeof(body));
}
bool RespondStatVFS(int chl, struct statvfs* stvfs)
{
struct fsm_resp_statvfs body;
body.stvfs = *stvfs;
return RespondMessage(chl, FSM_RESP_STATVFS, &body, sizeof(body));
}
bool RespondSeek(int chl, off_t offset)
{
struct fsm_resp_lseek body;
body.offset = offset;
return RespondMessage(chl, FSM_RESP_LSEEK, &body, sizeof(body));
}
bool RespondRead(int chl, const uint8_t* buf, size_t count)
{
struct fsm_resp_read body;
body.count = count;
return RespondMessage(chl, FSM_RESP_READ, &body, sizeof(body)) &&
RespondData(chl, buf, count);
}
bool RespondReadlink(int chl, const uint8_t* buf, size_t count)
{
struct fsm_resp_readlink body;
body.targetlen = count;
return RespondMessage(chl, FSM_RESP_READLINK, &body, sizeof(body)) &&
RespondData(chl, buf, count);
}
bool RespondWrite(int chl, size_t count)
{
struct fsm_resp_write body;
body.count = count;
return RespondMessage(chl, FSM_RESP_WRITE, &body, sizeof(body));
}
bool RespondOpen(int chl, ino_t ino, mode_t type)
{
struct fsm_resp_open body;
body.ino = ino;
body.type = type;
return RespondMessage(chl, FSM_RESP_OPEN, &body, sizeof(body));
}
bool RespondMakeDir(int chl, ino_t ino)
{
struct fsm_resp_mkdir body;
body.ino = ino;
return RespondMessage(chl, FSM_RESP_MKDIR, &body, sizeof(body));
}
bool RespondReadDir(int chl, struct dirent* dirent)
{
struct fsm_resp_readdirents body;
body.ino = dirent->d_ino;
body.type = dirent->d_type;
body.namelen = dirent->d_namlen;
return RespondMessage(chl, FSM_RESP_READDIRENTS, &body, sizeof(body)) &&
RespondData(chl, dirent->d_name, dirent->d_namlen);
}
bool RespondTCGetBlob(int chl, const void* data, size_t data_size)
{
struct fsm_resp_tcgetblob body;
body.count = data_size;
return RespondMessage(chl, FSM_RESP_TCGETBLOB, &body, sizeof(body)) &&
RespondData(chl, data, data_size);
}
Inode* SafeGetInode(Filesystem* fs, ino_t ino)
{
if ( (uint32_t) ino != ino )
return errno = EBADF, (Inode*) NULL;
return fs->GetInode((uint32_t) ino);
}
void HandleRefer(int chl, struct fsm_req_refer* msg, Filesystem* fs)
{
(void) chl;
if ( Inode* inode = SafeGetInode(fs, (uint32_t) msg->ino) )
{
if ( inode->implied_reference )
inode->implied_reference--;
else
inode->RemoteRefer();
inode->Unref();
}
}
void HandleUnref(int chl, struct fsm_req_unref* msg, Filesystem* fs)
{
(void) chl;
if ( Inode* inode = SafeGetInode(fs, (uint32_t) msg->ino) )
{
inode->RemoteUnref();
inode->Unref();
}
}
void HandleSync(int chl, struct fsm_req_sync* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
inode->Sync();
inode->Unref();
RespondSuccess(chl);
}
void HandleStat(int chl, struct fsm_req_stat* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
struct stat st;
StatInode(inode, &st);
inode->Unref();
RespondStat(chl, &st);
}
void HandleChangeMode(int chl, struct fsm_req_chmod* msg, Filesystem* fs)
{
if ( !fs->device->write ) { RespondError(chl, EROFS); return; }
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( !inode->ChangeMode(msg->mode) )
RespondError(chl, errno);
else
RespondSuccess(chl);
inode->Unref();
}
void HandleChangeOwner(int chl, struct fsm_req_chown* msg, Filesystem* fs)
{
if ( !fs->device->write ) { RespondError(chl, EROFS); return; }
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( !inode->ChangeOwner(msg->uid, msg->gid) )
RespondError(chl, errno);
else
RespondSuccess(chl);
inode->Unref();
}
void HandleUTimens(int chl, struct fsm_req_utimens* msg, Filesystem* fs)
{
if ( !fs->device->write ) { RespondError(chl, EROFS); return; }
Inode* inode = SafeGetInode(fs, msg->ino);
inode->UTimens(msg->times);
inode->Unref();
RespondSuccess(chl);
}
void HandleTruncate(int chl, struct fsm_req_truncate* msg, Filesystem* fs)
{
if ( !fs->device->write ) { RespondError(chl, EROFS); return; }
if ( msg->size < 0 ) { RespondError(chl, EINVAL); return; }
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
inode->Truncate((uint64_t) msg->size);
inode->Unref();
RespondSuccess(chl);
}
void HandleSeek(int chl, struct fsm_req_lseek* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( msg->whence == SEEK_SET )
RespondSeek(chl, msg->offset);
else if ( msg->whence == SEEK_END )
{
off_t inode_size = inode->Size();
if ( (msg->offset < 0 && inode_size + msg->offset < 0) ||
(0 <= msg->offset && OFF_MAX - inode_size < msg->offset) )
RespondError(chl, EOVERFLOW);
else
RespondSeek(chl, msg->offset + inode_size);
}
else
RespondError(chl, EINVAL);
inode->Unref();
}
void HandleReadAt(int chl, struct fsm_req_pread* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
uint8_t* buf = (uint8_t*) malloc(msg->count);
if ( !buf ) { inode->Unref(); RespondError(chl, errno); return; }
ssize_t amount = inode->ReadAt(buf, msg->count, msg->offset);
inode->Unref();
if ( amount < 0 ) { free(buf); RespondError(chl, errno); return; }
RespondRead(chl, buf, amount);
free(buf);
}
void HandleWriteAt(int chl, struct fsm_req_pwrite* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
const uint8_t* buf = (const uint8_t*) &msg[1];
ssize_t amount = inode->WriteAt(buf, msg->count, msg->offset);
inode->Unref();
if ( amount < 0 ) { RespondError(chl, errno); return; }
RespondWrite(chl, amount);
}
void HandleOpen(int chl, struct fsm_req_open* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
Inode* result = inode->Open(path, msg->flags, msg->mode & 07777);
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondOpen(chl, result->inode_id, result->Mode() & S_IFMT);
// TODO: Unfortunately Open does not implicitly imply RemoteRefer so we need
// to try and pretend that it does so the inode isn't destroyed early.
result->implied_reference++;
result->RemoteRefer();
result->Unref();
}
void HandleMakeDir(int chl, struct fsm_req_mkdir* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
Inode* result = inode->CreateDirectory(path, msg->mode & 07777);
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondMakeDir(chl, result->inode_id);
result->Unref();
}
// TODO: Encapsulate.
void HandleReadDir(int chl, struct fsm_req_readdirents* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( !S_ISDIR(inode->Mode()) )
{
inode->Unref();
RespondError(chl, ENOTDIR);
return;
}
union
{
struct dirent kernel_entry;
// TODO: Adjust with LFN's real limit.
uint8_t padding[sizeof(struct dirent) + 256];
};
memset(&kernel_entry, 0, sizeof(kernel_entry));
if ( inode->inode_id == inode->filesystem->root_inode_id )
{
if ( msg->rec_num < 2 )
{
const char* name = msg->rec_num ? ".." : ".";
size_t name_len = strlen(name);
kernel_entry.d_reclen = sizeof(kernel_entry) + name_len;
kernel_entry.d_ino = inode->inode_id;
kernel_entry.d_dev = 0;
kernel_entry.d_type = DT_DIR;
kernel_entry.d_namlen = name_len;
memcpy(kernel_entry.d_name, name, name_len);
size_t dname_offset = offsetof(struct dirent, d_name);
padding[dname_offset + kernel_entry.d_namlen] = '\0';
RespondReadDir(chl, &kernel_entry);
return;
}
msg->rec_num -= 2;
}
uint32_t cluster = inode->first_cluster;
uint8_t sector = 0;
uint16_t offset = 0;
Block* block = NULL;
while ( inode->Iterate(&block, &cluster, &sector, &offset) )
{
const uint8_t* block_data = block->block_data + offset;
const struct fat_dirent* entry = (const struct fat_dirent*) block_data;
if ( !entry->name[0] )
break;
if ( (unsigned char) entry->name[0] != 0xE5 &&
!(entry->attributes & FAT_ATTRIBUTE_VOLUME_ID) &&
!(msg->rec_num--) )
{
char name[8 + 1 + 3 + 1];
decode_8_3(entry->name, name);
size_t name_len = strnlen(entry->name, sizeof(entry->name));
uint8_t file_type =
entry->attributes & FAT_ATTRIBUTE_DIRECTORY ? DT_DIR : DT_REG;
kernel_entry.d_reclen = sizeof(kernel_entry) + name_len;
kernel_entry.d_ino = entry->cluster_low | entry->cluster_high << 16;
kernel_entry.d_dev = 0;
kernel_entry.d_type = file_type;
kernel_entry.d_namlen = name_len;
memcpy(kernel_entry.d_name, name, name_len);
size_t dname_offset = offsetof(struct dirent, d_name);
padding[dname_offset + kernel_entry.d_namlen] = '\0';
block->Unref();
inode->Unref();
RespondReadDir(chl, &kernel_entry);
return;
}
offset += sizeof(struct fat_dirent);
}
int errnum = errno;
if ( block )
block->Unref();
inode->Unref();
if ( errnum )
{
RespondError(chl, errnum);
return;
}
kernel_entry.d_reclen = sizeof(kernel_entry);
RespondReadDir(chl, &kernel_entry);
}
void HandleIsATTY(int chl, struct fsm_req_isatty* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
RespondError(chl, ENOTTY);
inode->Unref();
}
void HandleUnlink(int chl, struct fsm_req_unlink* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->Unlink(path, false);
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleRemoveDir(int chl, struct fsm_req_rmdir* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->RemoveDirectory(path);
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleLink(int chl, struct fsm_req_link* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
Inode* dest = SafeGetInode(fs, msg->linkino);
if ( !dest ) { inode->Unref(); RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->Link(path, dest, false);
free(path);
dest->Unref();
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleSymlink(int chl, struct fsm_req_symlink* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* dest_raw = (char*) &(msg[1]);
char* dest = (char*) malloc(msg->targetlen + 1);
if ( !dest )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(dest, dest_raw, msg->targetlen);
dest[msg->targetlen] = '\0';
char* path_raw = (char*) dest_raw + msg->targetlen;
char* path = (char*) malloc(msg->namelen + 1);
if ( !path )
{
free(dest);
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, path_raw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->Symlink(path, dest);
free(path);
free(dest);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleReadlink(int chl, struct fsm_req_readlink* msg, Filesystem* fs)
{
(void) msg;
(void) fs;
RespondError(chl, EINVAL);
}
void HandleRename(int chl, struct fsm_req_rename* msg, Filesystem* fs)
{
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->oldnamelen+1 + msg->newnamelen+1);
if ( !path ) { RespondError(chl, errno); return; }
memcpy(path, pathraw, msg->oldnamelen);
path[msg->oldnamelen] = '\0';
memcpy(path + msg->oldnamelen + 1, pathraw + msg->oldnamelen, msg->newnamelen);
path[msg->oldnamelen + 1 + msg->newnamelen] = '\0';
const char* oldname = path;
const char* newname = path + msg->oldnamelen + 1;
Inode* olddir = SafeGetInode(fs, msg->olddirino);
if ( !olddir ) { free(path); RespondError(chl, errno); return; }
Inode* newdir = SafeGetInode(fs, msg->newdirino);
if ( !newdir ) { olddir->Unref(); free(path); RespondError(chl, errno); return; }
bool result = newdir->Rename(olddir, oldname, newname);
newdir->Unref();
olddir->Unref();
free(path);
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleStatVFS(int chl, struct fsm_req_statvfs* msg, Filesystem* fs)
{
(void) msg;
struct statvfs stvfs;
stvfs.f_bsize = fs->cluster_size;
stvfs.f_frsize = fs->cluster_size;
stvfs.f_blocks = fs->cluster_count;
// TODO: Locate FsInfo and count on FAT12/FAT16.
stvfs.f_bfree = fs->CalculateFreeCount();
stvfs.f_bavail = stvfs.f_bfree;
stvfs.f_files = stvfs.f_blocks;
stvfs.f_ffree = stvfs.f_bfree;
stvfs.f_favail = stvfs.f_files;
stvfs.f_fsid = 0;
stvfs.f_flag = 0;
if ( !fs->device->write )
stvfs.f_flag |= ST_RDONLY;
stvfs.f_namemax = 8 + 3; // TODO: Long file name support.
RespondStatVFS(chl, &stvfs);
}
void HandleTCGetBlob(int chl, struct fsm_req_tcgetblob* msg, Filesystem* fs)
{
char* nameraw = (char*) &(msg[1]);
char* name = (char*) malloc(msg->namelen + 1);
if ( !name )
return (void) RespondError(chl, errno);
memcpy(name, nameraw, msg->namelen);
name[msg->namelen] = '\0';
static const char index[] =
"device-path\0filesystem-type\0filesystem-uuid\0mount-path\0"
"fat-size\0volume-id\0volume-label\0";
if ( !strcmp(name, "") )
RespondTCGetBlob(chl, index, sizeof(index) - 1);
else if ( !strcmp(name, "device-path") )
RespondTCGetBlob(chl, fs->device->path, strlen(fs->device->path));
else if ( !strcmp(name, "filesystem-type") )
RespondTCGetBlob(chl, "fat", strlen("fat"));
else if ( !strcmp(name, "fat-size") )
{
const char* str = fs->fat_type == 32 ? "32" :
fs->fat_type == 16 ? "16" : "12";
RespondTCGetBlob(chl, str, strlen(str));
}
else if ( !strcmp(name, "filesystem-uuid") )
{
unsigned char uuid[16];
if ( fs->fat_type == 32 )
{
memcpy(uuid, &fs->bpb->fat32_volume_id, 4);
memcpy(uuid + 4, &fs->bpb->fat32_volume_label, 11);
}
else
{
memcpy(uuid, &fs->bpb->fat12_volume_id, 4);
memcpy(uuid + 4, &fs->bpb->fat12_volume_label, 11);
}
uuid[15] = '\0';
RespondTCGetBlob(chl, uuid, sizeof(uuid));
}
else if ( !strcmp(name, "volume-id") )
{
if ( fs->fat_type == 32 )
RespondTCGetBlob(chl, &fs->bpb->fat32_volume_id, 4);
else
RespondTCGetBlob(chl, &fs->bpb->fat12_volume_id, 4);
}
else if ( !strcmp(name, "volume-label") )
{
if ( fs->fat_type == 32 )
RespondTCGetBlob(chl, &fs->bpb->fat32_volume_label, 11);
else
RespondTCGetBlob(chl, &fs->bpb->fat12_volume_label, 11);
}
else if ( !strcmp(name, "mount-path") )
RespondTCGetBlob(chl, fs->mount_path, strlen(fs->mount_path));
else
RespondError(chl, ENOENT);
free(name);
}
void HandleIncomingMessage(int chl, struct fsm_msg_header* hdr, Filesystem* fs)
{
request_uid = hdr->uid;
request_gid = hdr->gid;
typedef void (*handler_t)(int, void*, Filesystem*);
handler_t handlers[FSM_MSG_NUM] = { NULL };
handlers[FSM_REQ_SYNC] = (handler_t) HandleSync;
handlers[FSM_REQ_STAT] = (handler_t) HandleStat;
handlers[FSM_REQ_CHMOD] = (handler_t) HandleChangeMode;
handlers[FSM_REQ_CHOWN] = (handler_t) HandleChangeOwner;
handlers[FSM_REQ_TRUNCATE] = (handler_t) HandleTruncate;
handlers[FSM_REQ_LSEEK] = (handler_t) HandleSeek;
handlers[FSM_REQ_PREAD] = (handler_t) HandleReadAt;
handlers[FSM_REQ_OPEN] = (handler_t) HandleOpen;
handlers[FSM_REQ_READDIRENTS] = (handler_t) HandleReadDir;
handlers[FSM_REQ_PWRITE] = (handler_t) HandleWriteAt;
handlers[FSM_REQ_ISATTY] = (handler_t) HandleIsATTY;
handlers[FSM_REQ_UTIMENS] = (handler_t) HandleUTimens;
handlers[FSM_REQ_MKDIR] = (handler_t) HandleMakeDir;
handlers[FSM_REQ_RMDIR] = (handler_t) HandleRemoveDir;
handlers[FSM_REQ_UNLINK] = (handler_t) HandleUnlink;
handlers[FSM_REQ_LINK] = (handler_t) HandleLink;
handlers[FSM_REQ_SYMLINK] = (handler_t) HandleSymlink;
handlers[FSM_REQ_READLINK] = (handler_t) HandleReadlink;
handlers[FSM_REQ_RENAME] = (handler_t) HandleRename;
handlers[FSM_REQ_REFER] = (handler_t) HandleRefer;
handlers[FSM_REQ_UNREF] = (handler_t) HandleUnref;
handlers[FSM_REQ_STATVFS] = (handler_t) HandleStatVFS;
handlers[FSM_REQ_TCGETBLOB] = (handler_t) HandleTCGetBlob;
if ( FSM_MSG_NUM <= hdr->msgtype || !handlers[hdr->msgtype] )
{
warn("message type %zu not supported\n", hdr->msgtype);
RespondError(chl, ENOTSUP);
return;
}
uint8_t body_buffer[65536];
uint8_t* body = body_buffer;
if ( sizeof(body_buffer) < hdr->msgsize )
{
body = (uint8_t*) mmap(NULL, hdr->msgsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ( (void*) body == MAP_FAILED )
{
RespondError(chl, errno);
return;
}
}
if ( readall(chl, body, hdr->msgsize) == hdr->msgsize )
handlers[hdr->msgtype](chl, body, fs);
else
RespondError(chl, errno);
if ( sizeof(body_buffer) < hdr->msgsize )
munmap(body, hdr->msgsize);
}
static volatile bool should_terminate = false;
void TerminationHandler(int)
{
should_terminate = true;
}
static void ready(void)
{
const char* readyfd_env = getenv("READYFD");
if ( !readyfd_env )
return;
int readyfd = atoi(readyfd_env);
char c = '\n';
write(readyfd, &c, 1);
close(readyfd);
unsetenv("READYFD");
}
int fsmarshall_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev)
{
(void) argv0;
// Stat the root directory;
struct stat root_dir_st;
memset(&root_dir_st, 0, sizeof(root_dir_st));
root_dir_st.st_ino = fs->root_inode_id;
root_dir_st.st_mode = S_IFDIR | 0755;
// Create a filesystem server connected to the kernel that we'll listen on.
int serverfd = fsm_mountat(AT_FDCWD, mount_path, &root_dir_st, 0);
if ( serverfd < 0 )
err(1, "%s", mount_path);
// Make sure the server isn't unexpectedly killed and data is lost.
signal(SIGINT, TerminationHandler);
signal(SIGTERM, TerminationHandler);
signal(SIGQUIT, TerminationHandler);
// Become a background process in its own process group by default.
if ( !foreground )
{
pid_t child_pid = fork();
if ( child_pid < 0 )
err(1, "fork");
if ( child_pid )
exit(0);
setpgid(0, 0);
}
else
ready();
dev->SpawnSyncThread();
// Listen for filesystem messages and sync the filesystem every few seconds.
struct timespec last_sync_at;
clock_gettime(CLOCK_MONOTONIC, &last_sync_at);
int channel;
while ( 0 <= (channel = accept(serverfd, NULL, NULL)) )
{
if ( should_terminate )
break;
struct fsm_msg_header hdr;
size_t amount;
if ( (amount = readall(channel, &hdr, sizeof(hdr))) != sizeof(hdr) )
{
//warn("incomplete header: got %zi of %zu bytes", amount, sizeof(hdr));
errno = 0;
continue;
}
HandleIncomingMessage(channel, &hdr, fs);
close(channel);
if ( dev->write && !dev->has_sync_thread )
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if ( 5 <= timespec_sub(now, last_sync_at).tv_sec )
{
fs->Sync();
last_sync_at = now;
}
}
}
// TODO: Replace with FAT concept.
// Garbage collect all open inode references.
while ( fs->mru_inode )
{
Inode* inode = fs->mru_inode;
if ( inode->remote_reference_count )
inode->RemoteUnref();
else if ( inode->reference_count )
inode->Unref();
}
// Sync the filesystem before shutting down.
if ( dev->write )
fs->Sync();
close(serverfd);
delete fs;
delete dev;
return 0;
}
#endif

31
fat/fsmarshall.h Normal file
View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fsmarshall.h
* Sortix fsmarshall frontend.
*/
#ifndef FSMARSHALL_H
#define FSMARSHALL_H
class Device;
class Filesystem;
int fsmarshall_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev);
#endif

618
fat/fuse.cpp Normal file
View file

@ -0,0 +1,618 @@
/*
* Copyright (c) 2013, 2014, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fuse.cpp
* FUSE frontend.
*/
#if !defined(__sortix__)
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include "ext-constants.h"
#include "ext-structs.h"
#include "blockgroup.h"
#include "block.h"
#include "device.h"
#include "fatfs.h"
#include "filesystem.h"
#include "fuse.h"
#include "inode.h"
struct fat_fuse_ctx
{
Device* dev;
Filesystem* fs;
};
#ifndef S_SETABLE
#define S_SETABLE 02777
#endif
#define FUSE_FS (((struct fat_fuse_ctx*) (fuse_get_context()->private_data))->fs)
void* fat_fuse_init(struct fuse_conn_info* /*conn*/)
{
return fuse_get_context()->private_data;
}
void fat_fuse_destroy(void* fs_private)
{
struct fat_fuse_ctx* fat_fuse_ctx = (struct fat_fuse_ctx*) fs_private;
while ( fat_fuse_ctx->fs->mru_inode )
{
Inode* inode = fat_fuse_ctx->fs->mru_inode;
if ( inode->remote_reference_count )
inode->RemoteUnref();
else if ( inode->reference_count )
inode->Unref();
}
fat_fuse_ctx->fs->Sync();
fat_fuse_ctx->dev->Sync();
delete fat_fuse_ctx->fs; fat_fuse_ctx->fs = NULL;
delete fat_fuse_ctx->dev; fat_fuse_ctx->dev = NULL;
}
Inode* fat_fuse_resolve_path(const char* path)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode(FAT_ROOT_INO);
if ( !inode )
return (Inode*) NULL;
while ( path[0] )
{
if ( *path == '/' )
{
if ( !FAT_S_ISDIR(inode->Mode()) )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path++;
continue;
}
size_t elem_len = strcspn(path, "/");
char* elem = strndup(path, elem_len);
if ( !elem )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path += elem_len;
Inode* next = inode->Open(elem, O_RDONLY, 0);
free(elem);
inode->Unref();
if ( !next )
return NULL;
inode = next;
}
return inode;
}
// Assumes that the path doesn't end with / unless it's the root directory.
Inode* fat_fuse_parent_dir(const char** path_ptr)
{
const char* path = *path_ptr;
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode(FAT_ROOT_INO);
if ( !inode )
return (Inode*) NULL;
while ( strchr(path, '/') )
{
if ( *path == '/' )
{
if ( !FAT_S_ISDIR(inode->Mode()) )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path++;
continue;
}
size_t elem_len = strcspn(path, "/");
char* elem = strndup(path, elem_len);
if ( !elem )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path += elem_len;
Inode* next = inode->Open(elem, O_RDONLY, 0);
free(elem);
inode->Unref();
if ( !next )
return (Inode*) NULL;
inode = next;
}
*path_ptr = *path ? path : ".";
assert(!strchr(*path_ptr, '/'));
return inode;
}
int fat_fuse_getattr(const char* path, struct stat* st)
{
Inode* inode = fat_fuse_resolve_path(path);
if ( !inode )
return -errno;
StatInode(inode, st);
inode->Unref();
return 0;
}
int fat_fuse_fgetattr(const char* /*path*/, struct stat* st,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
StatInode(inode, st);
inode->Unref();
return 0;
}
int fat_fuse_readlink(const char* path, char* buf, size_t bufsize)
{
Inode* inode = fat_fuse_resolve_path(path);
if ( !inode )
return -errno;
if ( !FAT_S_ISLNK(inode->Mode()) )
return inode->Unref(), -(errno = EINVAL);
if ( !bufsize )
return inode->Unref(), -(errno = EINVAL);
ssize_t amount = inode->ReadAt((uint8_t*) buf, bufsize, 0);
if ( amount < 0 )
return inode->Unref(), -errno;
buf[(size_t) amount < bufsize ? (size_t) amount : bufsize - 1] = '\0';
inode->Unref();
return 0;
}
int fat_fuse_mknod(const char* path, mode_t mode, dev_t dev)
{
(void) path;
(void) mode;
(void) dev;
return -(errno = ENOSYS);
}
int fat_fuse_mkdir(const char* path, mode_t mode)
{
Inode* inode = fat_fuse_parent_dir(&path);
if ( !inode )
return -errno;
Inode* newdir = inode->CreateDirectory(path, ExtModeFromHostMode(mode));
inode->Unref();
if ( !newdir )
return -errno;
newdir->Unref();
return 0;
}
int fat_fuse_unlink(const char* path)
{
Inode* inode = fat_fuse_parent_dir(&path);
if ( !inode )
return -errno;
bool success = inode->Unlink(path, false);
inode->Unref();
return success ? 0 : -errno;
}
int fat_fuse_rmdir(const char* path)
{
Inode* inode = fat_fuse_parent_dir(&path);
if ( !inode )
return -errno;
bool success = inode->RemoveDirectory(path);
inode->Unref();
return success ? 0 : -errno;
}
int fat_fuse_symlink(const char* oldname, const char* newname)
{
Inode* newdir = fat_fuse_parent_dir(&newname);
if ( !newdir )
return -errno;
bool success = newdir->Symlink(newname, oldname);
newdir->Unref();
return success ? 0 : -errno;
}
int fat_fuse_rename(const char* oldname, const char* newname)
{
Inode* olddir = fat_fuse_parent_dir(&oldname);
if ( !olddir )
return -errno;
Inode* newdir = fat_fuse_parent_dir(&newname);
if ( !newdir )
return olddir->Unref(), -errno;
bool success = newdir->Rename(olddir, oldname, newname);
newdir->Unref();
olddir->Unref();
return success ? 0 : -errno;
}
int fat_fuse_link(const char* oldname, const char* newname)
{
Inode* inode = fat_fuse_resolve_path(oldname);
if ( !inode )
return -errno;
Inode* newdir = fat_fuse_parent_dir(&newname);
if ( !newdir )
return inode->Unref(), -errno;
bool success = inode->Link(newname, inode, false);
newdir->Unref();
inode->Unref();
return success ? 0 : -errno;
}
int fat_fuse_chmod(const char* path, mode_t mode)
{
Inode* inode = fat_fuse_resolve_path(path);
if ( !inode )
return -errno;
if ( !FUSE_FS->device->write )
return inode->Unref(), -(errno = EROFS);
uint32_t req_mode = ExtModeFromHostMode(mode);
uint32_t old_mode = inode->Mode();
uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE);
inode->SetMode(new_mode);
inode->Unref();
return 0;
}
int fat_fuse_chown(const char* path, uid_t owner, gid_t group)
{
Inode* inode = fat_fuse_resolve_path(path);
if ( !inode )
return -errno;
if ( !FUSE_FS->device->write )
return inode->Unref(), -(errno = EROFS);
inode->SetUserId((uint32_t) owner);
inode->SetGroupId((uint32_t) group);
inode->Unref();
return 0;
}
int fat_fuse_truncate(const char* path, off_t size)
{
Inode* inode = fat_fuse_resolve_path(path);
if ( !inode )
return -errno;
if ( !FUSE_FS->device->write )
return inode->Unref(), -(errno = EROFS);
inode->Truncate((uint64_t) size);
inode->Unref();
return 0;
}
int fat_fuse_ftruncate(const char* /*path*/, off_t size,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
if ( !FUSE_FS->device->write )
return inode->Unref(), -(errno = EROFS);
inode->Truncate((uint64_t) size);
inode->Unref();
return 0;
}
int fat_fuse_open(const char* path, struct fuse_file_info* fi)
{
int flags = fi->flags;
Inode* dir = fat_fuse_parent_dir(&path);
if ( !dir )
return -errno;
Inode* result = dir->Open(path, flags, 0);
dir->Unref();
if ( !result )
return -errno;
fi->fh = (uint64_t) result->inode_id;
fi->keep_cache = 1;
result->RemoteRefer();
result->Unref();
return 0;
}
int fat_fuse_access(const char* path, int mode)
{
Inode* dir = fat_fuse_parent_dir(&path);
if ( !dir )
return -errno;
Inode* result = dir->Open(path, O_RDONLY, 0);
dir->Unref();
if ( !result )
return -errno;
(void) mode;
result->Unref();
return 0;
}
int fat_fuse_create(const char* path, mode_t mode, struct fuse_file_info* fi)
{
int flags = fi->flags | O_CREAT;
Inode* inode = fat_fuse_parent_dir(&path);
if ( !inode )
return -errno;
Inode* result = inode->Open(path, flags, ExtModeFromHostMode(mode));
inode->Unref();
if ( !result )
return -errno;
fi->fh = (uint64_t) result->inode_id;
fi->keep_cache = 1;
result->RemoteRefer();
result->Unref();
return 0;
}
int fat_fuse_opendir(const char* path, struct fuse_file_info* fi)
{
return fat_fuse_open(path, fi);
}
int fat_fuse_read(const char* /*path*/, char* buf, size_t count, off_t offset,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
if ( INT_MAX < count )
count = INT_MAX;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
ssize_t result = inode->ReadAt((uint8_t*) buf, count, offset);
inode->Unref();
return 0 <= result ? (int) result : -errno;
}
int fat_fuse_write(const char* /*path*/, const char* buf, size_t count,
off_t offset, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
if ( INT_MAX < count )
count = INT_MAX;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
ssize_t result = inode->WriteAt((const uint8_t*) buf, count, offset);
inode->Unref();
return 0 <= result ? (int) result : -errno;
}
int fat_fuse_statfs(const char* /*path*/, struct statvfs* stvfs)
{
memset(stvfs, 0, sizeof(*stvfs));
Filesystem* fs = FUSE_FS;
stvfs->f_bsize = fs->block_size;
stvfs->f_frsize = fs->block_size;
stvfs->f_blocks = fs->num_blocks;
stvfs->f_bfree = fs->sb->s_free_blocks_count;
stvfs->f_bavail = fs->sb->s_free_blocks_count;
stvfs->f_files = fs->num_inodes;
stvfs->f_ffree = fs->sb->s_free_inodes_count;
stvfs->f_favail = fs->sb->s_free_inodes_count;
stvfs->f_ffree = fs->sb->s_free_inodes_count;
stvfs->f_fsid = 0;
stvfs->f_flag = 0;
if ( !fs->device->write )
stvfs->f_flag |= ST_RDONLY;
stvfs->f_namemax = 255;
return 0;
}
int fat_fuse_flush(const char* /*path*/, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->Sync();
inode->Unref();
return 0;
}
int fat_fuse_release(const char* /*path*/, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->RemoteUnref();
inode->Unref();
return 0;
}
int fat_fuse_releasedir(const char* path, struct fuse_file_info* fi)
{
return fat_fuse_release(path, fi);
}
int fat_fuse_fsync(const char* /*path*/, int data, struct fuse_file_info* fi)
{
(void) data;
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->Sync();
inode->Unref();
return 0;
}
/*int fat_fuse_syncdir(const char* path, int data, struct fuse_file_info* fi)
{
return fat_fuse_sync(path, data, fi);
}*/
/*int fat_fuse_setxattr(const char *, const char *, const char *, size_t, int)
{
return -(errno = ENOSYS);
}*/
/*int fat_fuse_getxattr(const char *, const char *, char *, size_t)
{
return -(errno = ENOSYS);
}*/
/*int fat_fuse_listxattr(const char *, char *, size_t)
{
return -(errno = ENOSYS);
}*/
/*int fat_fuse_removexattr(const char *, const char *)
{
return -(errno = ENOSYS);
}*/
int fat_fuse_readdir(const char* /*path*/, void* buf, fuse_fill_dir_t filler,
off_t rec_num, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
if ( !S_ISDIR(inode->Mode()) )
return inode->Unref(), -(errno = ENOTDIR);
uint64_t file_size = inode->Size();
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
while ( offset < file_size )
{
uint64_t entry_block_id = offset / fs->block_size;
uint64_t entry_block_offset = offset % fs->block_size;
if ( block && block_id != entry_block_id )
block->Unref(),
block = NULL;
if ( !block && !(block = inode->GetBlock(block_id = entry_block_id)) )
return inode->Unref(), -errno;
const uint8_t* block_data = block->block_data + entry_block_offset;
const struct ext_dirent* entry = (const struct ext_dirent*) block_data;
if ( entry->inode && entry->name_len && (!rec_num || !rec_num--) )
{
char* entry_name = strndup(entry->name, entry->name_len);
if ( !entry_name )
return block->Unref(), inode->Unref(), -errno;
memcpy(entry_name, entry->name, entry->name_len);
bool full = filler(buf, entry_name, NULL, 0);
free(entry_name);
if ( full )
{
block->Unref();
inode->Unref();
return 0;
}
}
offset += entry->reclen;
}
if ( block )
block->Unref();
inode->Unref();
return 0;
}
/*int fat_fuse_lock(const char*, struct fuse_file_info*, int, struct flock*)
{
return -(errno = ENOSYS);
}*/
int fat_fuse_utimens(const char* path, const struct timespec tv[2])
{
Inode* inode = fat_fuse_resolve_path(path);
if ( !inode )
return -errno;
if ( !FUSE_FS->device->write )
return inode->Unref(), -(errno = EROFS);
inode->BeginWrite();
inode->data->i_atime = tv[0].tv_sec;
inode->data->i_mtime = tv[1].tv_sec;
inode->FinishWrite();
inode->Unref();
return 0;
}
/*int fat_fuse_bmap(const char*, size_t blocksize, uint64_t* idx)
{
return -(errno = ENOSYS);
}*/
int fat_fuse_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev)
{
struct fuse_operations operations;
memset(&operations, 0, sizeof(operations));
operations.access = fat_fuse_access;
operations.chmod = fat_fuse_chmod;
operations.chown = fat_fuse_chown;
operations.create = fat_fuse_create;
operations.destroy = fat_fuse_destroy;
operations.fgetattr = fat_fuse_fgetattr;
operations.flush = fat_fuse_flush;
operations.fsync = fat_fuse_fsync;
operations.ftruncate = fat_fuse_ftruncate;
operations.getattr = fat_fuse_getattr;
operations.init = fat_fuse_init;
operations.link = fat_fuse_link;
operations.mkdir = fat_fuse_mkdir;
operations.mknod = fat_fuse_mknod;
operations.opendir = fat_fuse_opendir;
operations.open = fat_fuse_open;
operations.readdir = fat_fuse_readdir;
operations.read = fat_fuse_read;
operations.readlink = fat_fuse_readlink;
operations.releasedir = fat_fuse_releasedir;
operations.release = fat_fuse_release;
operations.rename = fat_fuse_rename;
operations.rmdir = fat_fuse_rmdir;
operations.statfs = fat_fuse_statfs;
operations.symlink = fat_fuse_symlink;
operations.truncate = fat_fuse_truncate;
operations.unlink = fat_fuse_unlink;
operations.utimens = fat_fuse_utimens;
operations.write = fat_fuse_write;
operations.flag_nullpath_ok = 1;
operations.flag_nopath = 1;
char* argv_fuse[] =
{
(char*) argv0,
(char*) "-s",
(char*) mount_path,
(char*) NULL,
};
int argc_fuse = sizeof(argv_fuse) / sizeof(argv_fuse[0]) - 1;
struct fat_fuse_ctx fat_fuse_ctx;
fat_fuse_ctx.fs = fs;
fat_fuse_ctx.dev = dev;
(void) foreground;
return fuse_main(argc_fuse, argv_fuse, &operations, &fat_fuse_ctx);
}
#endif

32
fat/fuse.h Normal file
View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fuse.h
* FUSE frontend.
*/
#ifndef FUSE_H
#define FUSE_H
class Device;
class Filesystem;
int fat_fuse_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev);
#endif

1015
fat/inode.cpp Normal file

File diff suppressed because it is too large Load diff

90
fat/inode.h Normal file
View file

@ -0,0 +1,90 @@
/*
* Copyright (c) 2013, 2014, 2015, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* inode.h
* Filesystem inode.
*/
#ifndef INODE_H
#define INODE_H
class Block;
class Filesystem;
class Inode
{
public:
Inode(Filesystem* filesystem, uint32_t inode_id);
~Inode();
public:
Inode* prev_inode;
Inode* next_inode;
Inode* prev_hashed;
Inode* next_hashed;
Inode* prev_dirty;
Inode* next_dirty;
Block* data_block;
struct fat_dirent* dirent;
struct fat_dirent deleted_dirent;
uint32_t first_cluster;
Filesystem* filesystem;
size_t reference_count;
size_t remote_reference_count;
size_t implied_reference;
uint32_t inode_id;
bool dirty;
bool deleted;
public:
uint32_t Mode();
uint32_t UserId();
uint32_t GroupId();
uint64_t Size();
void UTimens(const struct timespec times[2]);
bool ChangeMode(mode_t mode);
bool ChangeOwner(uid_t uid, gid_t gid);
bool Truncate(uint64_t new_size);
Block* GetClusterSector(uint32_t cluster, uint8_t sector);
bool Iterate(Block** block_ptr, uint32_t* cluster_ptr,
uint8_t* sector_ptr, uint16_t* offset);
uint32_t SeekCluster(uint32_t cluster_id);
Inode* Open(const char* elem, int flags, mode_t mode);
bool Link(const char* elem, Inode* dest, bool directories);
bool Symlink(const char* elem, const char* dest);
bool Unlink(const char* elem, bool directories, bool force=false);
Inode* UnlinkKeep(const char* elem, bool directories, bool force=false);
ssize_t ReadAt(uint8_t* buffer, size_t count, off_t offset);
ssize_t WriteAt(const uint8_t* buffer, size_t count, off_t offset);
bool Rename(Inode* olddir, const char* oldname, const char* newname);
Inode* CreateDirectory(const char* path, mode_t mode);
bool RemoveDirectory(const char* path);
bool IsEmptyDirectory();
void Refer();
void Unref();
void RemoteRefer();
void RemoteUnref();
void Sync();
void BeginWrite();
void FinishWrite();
void Modified();
void Use();
void Unlink();
void Prelink();
void Delete();
};
#endif

150
fat/ioleast.h Normal file
View file

@ -0,0 +1,150 @@
/*
* Copyright (c) 2012, 2013, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ioleast.h
* Versions of {,p}{read,write} that don't return until it has returned as much
* data as requested, end of file, or an error occurs. This is sometimes needed
* as read(2) and write(2) is not always guaranteed to fill up the entire
* buffer or write it all.
*/
#ifndef SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H
#define SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H
#if defined(__sortix__) || defined(__sortix_libc__)
#include_next <ioleast.h>
#else
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>
#if !defined(EEOF) && defined(EIO)
#define EEOF EIO
#endif
__attribute__((unused)) static inline
size_t readleast(int fd, void* buf_ptr, size_t least, size_t max)
{
assert(least <= max);
unsigned char* buf = (unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = read(fd, buf + done, max - done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t writeleast(int fd, const void* buf_ptr, size_t least, size_t max)
{
assert(least <= max);
const unsigned char* buf = (const unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = write(fd, buf + done, max - done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t preadleast(int fd, void* buf_ptr, size_t least, size_t max, off_t off)
{
assert(least <= max);
unsigned char* buf = (unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = pread(fd, buf + done, max - done, off + done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t pwriteleast(int fd, const void* buf_ptr, size_t least, size_t max, off_t off)
{
assert(least <= max);
const unsigned char* buf = (const unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = pwrite(fd, buf + done, max - done, off + done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t readall(int fd, void* buf, size_t count)
{
return readleast(fd, buf, count, count);
}
__attribute__((unused)) static inline
size_t writeall(int fd, const void* buf, size_t count)
{
return writeleast(fd, buf, count, count);
}
__attribute__((unused)) static inline
size_t preadall(int fd, void* buf, size_t count, off_t off)
{
return preadleast(fd, buf, count, count, off);
}
__attribute__((unused)) static inline
size_t pwriteall(int fd, const void* buf, size_t count, off_t off)
{
return pwriteleast(fd, buf, count, count, off);
}
#endif
#endif

49
fat/util.h Normal file
View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2013, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* util.h
* Utility functions for the filesystem implementation.
*/
#ifndef UTIL_H
#define UTIL_H
template <class T> T divup(T a, T b)
{
return a/b + (a % b ? 1 : 0);
}
template <class T> T roundup(T a, T b)
{
return a % b ? a + b - a % b : a;
}
static inline bool checkbit(const uint8_t* bitmap, size_t bit)
{
uint8_t bits = bitmap[bit / 8UL];
return bits & (1U << (bit % 8UL));
}
static inline void setbit(uint8_t* bitmap, size_t bit)
{
bitmap[bit / 8UL] |= 1U << (bit % 8UL);
}
static inline void clearbit(uint8_t* bitmap, size_t bit)
{
bitmap[bit / 8UL] &= ~(1U << (bit % 8UL));
}
#endif

1
init/.gitignore vendored
View file

@ -1,2 +1,3 @@
init
service
*.o

View file

@ -8,26 +8,26 @@ CFLAGS?=$(OPTLEVEL)
CFLAGS:=$(CFLAGS) -Wall -Wextra
BINARY=init
BINARIES=\
init \
service \
OBJS=\
init.o \
MANPAGES8=\
init.8 \
service.8 \
all: $(BINARY)
all: $(BINARIES)
.PHONY: all install clean
$(BINARY): $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o $(BINARY) -lmount $(LIBS)
%.o: %.c
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
%: %.c
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@ -lmount $(LIBS)
install: all
mkdir -p $(DESTDIR)$(SBINDIR)
install $(BINARY) $(DESTDIR)$(SBINDIR)
install $(BINARIES) $(DESTDIR)$(SBINDIR)
mkdir -p $(DESTDIR)$(MANDIR)/man8
cp init.8 $(DESTDIR)$(MANDIR)/man8/init.8
install $(MANPAGES8) $(DESTDIR)$(MANDIR)/man8
clean:
rm -f $(BINARY) $(OBJS) *.o
rm -f $(BINARIES) $(OBJS) *.o

View file

@ -213,12 +213,9 @@ daemon is meant to start the installation's local daemon requirements.
.Sh ENVIRONMENT
.Nm
sets the following environment variables.
.Bl -tag -width "INIT_PID"
.Bl -tag -width "LOGNAME"
.It Ev HOME
root's home directory
.It Ev INIT_PID
.Nm Ns 's
process id
.It Ev LOGNAME
root
.It Ev PATH
@ -301,6 +298,7 @@ exits with the same exit status as its target session if it terminates normally.
.Xr login 8 ,
.Xr poweroff 8 ,
.Xr reboot 8 ,
.Xr service 8 ,
.Xr sysmerge 8 ,
.Xr update-initrd 8
.Sh SECURITY CONSIDERATIONS

File diff suppressed because it is too large Load diff

391
init/service.8 Normal file
View file

@ -0,0 +1,391 @@
.Dd May 24, 2024
.Dt SERVICE 8
.Os
.Sh NAME
.Nm service
.Nd daemon maintenance
.Sh SYNOPSIS
.Nm service
.Op Fl lr
.Op Fl \-s Ns "=" Ns source-daemon
.Op Fl \-exit-code
.Op Fl \-no-await
.Op Fl \-no-optional
.Op Fl \-raw
.Op Fl \-source Ns "=" Ns source-daemon
.Ar daemon
.Oo
.Sy dependents
|
.Sy disable
|
.Sy edges
|
.Sy enable
|
.Sy exit-code
|
.Sy kill
|
.Sy pid
|
.Sy reconfigure
|
.Sy reload
|
.Sy requirements
|
.Sy restart
|
.Sy signal
|
.Sy start
|
.Sy state
|
.Sy status
|
.Sy stop
|
.Sy terminate
.Oc
.Sh DESCRIPTION
.Nm
performs maintenance of daemons run by
.Xr init 8
as configured in
.Xr init 5 .
The daemons are serviced by connecting to the
.Nm init
process and writing the requested command to the
.Pa /var/run/init
filesytem socket.
.Pp
The options are as follows:
.Bl -tag -width "12345678"
.It Fl s , Fl \-source-daemon Ns "=" Ns Ar source-daemon
When modifying a dependency using the
.Sy enable , disable , start
and
.Sy stop
commands, use the
.Ar source-daemon
as the source daemon in the dependency on the target
.Ar daemon .
The default the
.Sy local
daemon which is the parent of the locally configured daemons.
.It Fl \-exit-code
Set the
.Sy exit-code
flag on the dependency created in the
.Sy enable
and
.Sy start
commands.
.It Fl \-no-await
Set the
.Sy no-await
flag on the dependency created in the
.Sy enable
and
.Sy start
commands.
.It Fl \-no-optional
Unset the
.Sy optional
flag on the dependency created in the
.Sy enable
and
.Sy start
commands.
The default is to set the
.Sy optional
flag, which is the opposite of the
.Xr init 5
.Sy require
declaration where dependencies are mandatory by default.
.It Fl l , \-list
Write a table containing the status of every loaded daemon in the format of the
.Sy status
command.
.It Fl r , \-raw
Write the command and additional operands as a raw message without verification
on the
.Nm init
socket
and output the raw reply sent from
.Nm init.
.El
.Pp
The commands are as follows:
.Bl -tag -width "requirements"
.It Sy dependents
Write the incoming dependencies on the
.Ar daemon
in the format of the
.Sy edges
command, which explains why a daemon is running.
.It Sy disable
Permanently disable the
.Ar daemon
by removing the dependency on it from the configuration of the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
and then stopping it using the
.Sy stop
command.
The daemon will continue to run if any other daemon depends on it, which can
be diagnosed using the
.Sy dependents
command.
.It Sy edges
Write the incoming dependencies on the
.Ar daemon
and its outgoing dependencies in the format of the
.Sy require
declaration in
.Xr init 5
with an extra
.Ar source
operand:
.Pp
.Sy require
.Ar source
.Ar target
[exit-code]
[no-await]
[optional]
.It Sy enable
Permanently enable the
.Ar daemon
by adding it to the configuration of the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
with the dependency flags per
.Fl \-exit-code ,
.Fl \-no-await ,
and
.Fl \-no-optional
and starting it by sending the
.Sy start
command.
The daemon only starts if the
.Ar source-daemon
is running.
.It Sy exit-code
Write the exit code of the
.Ar daemon ,
or the empty string if it has not exited.
.It Sy kill
Kill the
.Ar daemon
by sending the
.Sy SIGKILL
signal as a last resort that may cause data loss.
.It Sy pid
Write the process id of the
.Ar daemon
if it is running, or the empty output if it
does not have a process.
Process ids can be recycled and are subject to inherent race conditions.
Prefer to use the other commands in
.Nm
that addresses the daemon by its symbolic name as
.Xr init 8
will ensure the command operates on the correct process.
.It Sy reconfigure
Reread the
.Xr init 5
configuration for the
.Ar daemon
and apply it after restarting the daemon.
.It Sy reload
Request the
.Ar daemon
gracefully reload its own configuration sending
the reload signal (usually
.Sy SIGHUP )
without restarting the daemon and without reloading the
.Xr init 5
configuration.
.It Sy requirements
Write the outgoing dependencies from the
.Ar daemon
in the format of the
.Sy edges
command, which explains what daemons the daemon needs.
.It Sy restart
Restart the
.Ar daemon
by terminating it and starting it up again afterwards.
.It Sy signal Ar signal
Send the
.Ar signal
in the symbolic signal name format (e.g.
.Sy SIGUSR1 )
to the
.Ar daemon .
.It Sy start
Start the
.Ar daemon
by asking
.Xr init 8
to add a runtime dependency from the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
to the daemon with the dependency flags per
.Fl \-exit-code ,
.Fl \-no-await ,
and
.Fl \-no-optional
and starting it by sending the
.Sy start
command.
The daemon only starts if the
.Ar source-daemon
is running.
.It Sy state
Write which the state the
.Ar daemon
is in.
.It Sy status
Write the status of the
.Ar daemon
as a single table row in the format:
.Pp
.Ar name
.Ar state
.Li pid Ns "=" Ns Ar pid
.Li exit Ns "=" Ns Ar exit-code
.Pp
The
.Ar state
is one of
.Sy terminated ,
.Sy scheduled ,
.Sy waiting ,
.Sy satisfied ,
.Sy starting ,
.Sy running ,
.Sy terminating ,
.Sy finishing ,
.Sy finished ,
or
.Sy failed .
The
.Ar pid
is the process id if any, or
.Li 0
otherwise.
The
.Ar exit-code
is the exit code of the daemon if it has exited, or the name of a signal that
killed it, or
.Li n/a
if the daemon has not exited.
.It Sy stop
Stop the
.Ar daemon
by asking
.Xr init 8
to remove the dependency from the
.Ar source-daemon
(the
.Sy local
daemon by default, see
.Xr init 5 )
on the daemon.
The daemon will continue to run as long if other daemon depends on it, which can
be diagnosed using the
.Sy dependents
command.
.It Sy terminate
Terminate the
.Ar daemon
gracefully by sending the
.Sy SIGTERM
signal and
.Sy SIGKILL
after a timeout.
Prefer the
.Sy stop
command if possible as the
.Sy terminate
command bypasses the reference count and may cause data loss if other daemons
malfunction when the daemon is unexpectedly terminated.
.El
.Sh ENVIRONMENT
.Bl -tag -width "INIT_SOCKET"
.It Ev INIT_SOCKET
.Xr init 8 Ns 's
filesystem socket for communication,
.Pa /var/run/init
by default.
.El
.Sh FILES
.Bl -tag -width "/etc/init/local" -compact
.It Pa /etc/init/
Daemon configuration for the local system (first in search path) (see
.Xr init 5 )
.It Pa /etc/init/local
Configuration for the
.Sy local
daemon (see
.Xr init 5 )
.El
.Sh EXIT STATUS
.Nm
will exit 0 on success and non-zero otherwise.
.Sh EXAMPLES
Permanently enable the sshd daemon:
.Bd -literal
$ service sshd enable
.Ed
.Pp
Permanently disable the ntpd daemon:
.Bd -literal
$ service ntpd disable
.Ed
.Pp
Temporarily start the nginx daemon without changing
.Pa /etc/init/local :
.Bd -literal
$ service nginx start
.Ed
.Pp
Temporarily disable the sshd daemon without changing
.Pa /etc/init/local :
.Bd -literal
$ service sshd stop
.Ed
.Pp
Temporarily stop the ntpd daemon and diagnose why it kept running due to the
.Sy time
daemon depending on it:
.Bd -literal
$ service ntpd stop
$ service ntpd state
running
$ service ntpd dependents
require time ntpd exit-code
$ service --source-daemon=time ntpd stop
$ service ntpd state
finished
.Ed
.Sh SEE ALSO
.Xr kill 1 ,
.Xr init 5 ,
.Xr halt 8 ,
.Xr init 8 ,
.Xr poweroff 8 ,
.Xr reboot 8

594
init/service.c Normal file
View file

@ -0,0 +1,594 @@
/*
* Copyright (c) 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* service.c
* Start and stop services.
*/
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <wchar.h>
static bool array_add(void*** array_ptr,
size_t* used_ptr,
size_t* length_ptr,
void* value)
{
void** array;
memcpy(&array, array_ptr, sizeof(array)); // Strict aliasing.
if ( *used_ptr == *length_ptr )
{
size_t length = *length_ptr;
if ( !length )
length = 4;
void** new_array = reallocarray(array, length, 2 * sizeof(void*));
if ( !new_array )
return false;
array = new_array;
memcpy(array_ptr, &array, sizeof(array)); // Strict aliasing.
*length_ptr = length * 2;
}
memcpy(array + (*used_ptr)++, &value, sizeof(value)); // Strict aliasing.
return true;
}
static char** tokenize(size_t* out_tokens_used, const char* string)
{
size_t tokens_used = 0;
size_t tokens_length = 0;
char** tokens = malloc(sizeof(char*));
if ( !tokens )
return NULL;
bool failed = false;
bool invalid = false;
while ( *string )
{
if ( isspace((unsigned char) *string) )
{
string++;
continue;
}
if ( *string == '#' )
break;
char* token;
size_t token_size;
FILE* fp = open_memstream(&token, &token_size);
if ( !fp )
{
failed = true;
break;
}
bool singly = false;
bool doubly = false;
bool escaped = false;
while ( *string )
{
char c = *string++;
if ( !escaped && !singly && !doubly && isspace((unsigned char) c) )
break;
if ( !escaped && !doubly && c == '\'' )
{
singly = !singly;
continue;
}
if ( !escaped && !singly && c == '"' )
{
doubly = !doubly;
continue;
}
if ( !singly && !escaped && c == '\\' )
{
escaped = true;
continue;
}
if ( escaped )
{
switch ( c )
{
case 'a': c = '\a'; break;
case 'b': c = '\b'; break;
case 'e': c = '\e'; break;
case 'f': c = '\f'; break;
case 'n': c = '\n'; break;
case 'r': c = '\r'; break;
case 't': c = '\t'; break;
case 'v': c = '\v'; break;
default: break;
};
}
escaped = false;
if ( fputc((unsigned char) c, fp) == EOF )
{
failed = true;
break;
}
}
if ( singly || doubly || escaped )
{
fclose(fp);
free(token);
invalid = true;
break;
}
if ( fflush(fp) == EOF )
{
fclose(fp);
free(token);
failed = true;
break;
}
fclose(fp);
if ( !array_add((void***) &tokens, &tokens_used, &tokens_length,
token) )
{
free(token);
failed = true;
break;
}
}
if ( failed || invalid )
{
for ( size_t i = 0; i < tokens_used; i++ )
free(tokens[i]);
free(tokens);
if ( invalid )
errno = 0;
return NULL;
}
char** new_tokens = reallocarray(tokens, tokens_used, sizeof(char*));
if ( new_tokens )
tokens = new_tokens;
*out_tokens_used = tokens_used;
return tokens;
}
static char** receive(FILE* fp, size_t* out_tokens_used)
{
char* line = NULL;
size_t line_size;
if ( getline(&line, &line_size, fp) < 0 )
{
if ( ferror(fp) )
errx(1, "receiving reply: Unexpected end of connection");
else
err(1, "receiving reply");
}
char** result = tokenize(out_tokens_used, line);
free(line);
if ( !result )
{
if ( errno )
errx(1, "invalid reply: %s", line);
else
errx(1, "failed to parse replyt");
}
if ( !*out_tokens_used )
errx(1, "invalid empty reply");
if ( !strcmp(result[0], "ok") )
return result;
else if ( !strcmp(result[0], "error") )
errx(1, "error: %s", 2 <= *out_tokens_used ? result[1] : "Unknown");
else
errx(1, "unknown reply: %s", result[0]);
}
static int open_local_client_socket(const char* path, int flags)
{
size_t path_length = strlen(path);
size_t addr_size = offsetof(struct sockaddr_un, sun_path) + path_length + 1;
struct sockaddr_un* sockaddr = malloc(addr_size);
if ( !sockaddr )
return -1;
sockaddr->sun_family = AF_LOCAL;
strcpy(sockaddr->sun_path, path);
int fd = socket(AF_LOCAL, SOCK_STREAM | flags, 0);
if ( fd < 0 )
return free(sockaddr), -1;
if ( connect(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 )
return close(fd), free(sockaddr), -1;
free(sockaddr);
return fd;
}
static void rewrite(const char* path, const char* daemon, const char* flags)
{
FILE* fp = fopen(path, "r");
if ( !fp && errno != ENOENT )
err(1, "%s", path);
char* out_path;
if ( asprintf(&out_path, "%s.XXXXXX", path) < 0 )
err(1, "malloc");
int out_fd = mkstemp(out_path);
if ( out_fd < 0 )
err(1, "mkstemp: %s.XXXXXX", path);
FILE* out = fdopen(out_fd, "w");
if ( !out )
{
unlink(out_path);
err(1, "fdopen");
}
bool found = false;
char* line = NULL;
size_t line_size = 0;
ssize_t line_length;
off_t line_number = 0;
while ( fp && 0 < (line_length = getline(&line, &line_size, fp)) )
{
line_number++;
size_t tokenc;
char** tokens = tokenize(&tokenc, line);
if ( !tokens )
{
unlink(out_path);
if ( errno )
err(1, "%s", path);
else
errx(1, "%s:%ji: Syntax error", path, (intmax_t) line_number);
}
if ( 2 <= tokenc &&
!strcmp(tokens[0], "require") && !strcmp(tokens[1], daemon) )
{
found = true;
if ( flags )
fprintf(out, "require %s%s\n", daemon, flags);
}
else
fputs(line, out);
}
free(line);
if ( !found && flags )
fprintf(out, "require %s%s\n", daemon, flags);
if ( (fp && ferror(fp)) || ferror(out) || fflush(out) == EOF )
{
unlink(out_path);
err(1, "%s", path);
}
if ( fp )
{
struct stat st;
fstat(fileno(fp), &st);
fchmod(out_fd, st.st_mode & 07777);
fchown(out_fd, st.st_uid, st.st_gid);
fclose(fp);
}
else
fchmod(out_fd, 0666 & ~getumask());
if ( rename(out_path, path) < 0 )
{
unlink(out_path);
err(1, "rename: %s -> %s", out_path, path);
}
fclose(out);
}
static bool check_daemon_exists_in_dir(const char* dir, const char* daemon)
{
char* path;
if ( asprintf(&path, "%s/%s", dir, daemon) < 0 )
err(1, "malloc");
bool result = !access(path, F_OK);
free(path);
return result;
}
static void check_daemon_exists(const char* daemon)
{
if ( !check_daemon_exists_in_dir("/etc/init", daemon) &&
!check_daemon_exists_in_dir("/share/init", daemon) )
errx(1, "%s: Daemon does not exist", daemon);
}
static size_t string_display_length(const char* str)
{
size_t display_length = 0;
mbstate_t ps;
memset(&ps, 0, sizeof(ps));
while ( true )
{
wchar_t wc;
size_t amount = mbrtowc(&wc, str, SIZE_MAX, &ps);
if ( amount == 0 )
break;
if ( amount == (size_t) -1 || amount == (size_t) -2 )
{
display_length++;
str++;
memset(&ps, 0, sizeof(ps));
continue;
}
int width = wcwidth(wc);
if ( width < 0 )
width = 0;
if ( SIZE_MAX - display_length < (size_t) width )
display_length = SIZE_MAX;
else
display_length += (size_t) width;
str += amount;
}
return display_length;
}
static void pad(const char* string, size_t padding)
{
fputs(string, stdout);
for ( size_t length = string_display_length(string);
length < padding; padding-- )
putchar(' ');
}
static void format_statuses(char** tokens, size_t count)
{
size_t daemon_length = 0;
size_t state_length = 10;
for ( size_t i = 0; i < count; i++ )
{
if ( !strncmp(tokens[i], "daemon=", strlen("daemon=")) )
{
const char* arg = tokens[i] + strlen("daemon=");
size_t length = string_display_length (arg);
if ( daemon_length < length )
daemon_length = length;
}
else if ( !strncmp(tokens[i], "state=", strlen("state=")) )
{
const char* arg = tokens[i] + strlen("state=");
size_t length = string_display_length(arg);
if ( state_length < length )
state_length = length;
}
}
size_t i = 0;
while ( i < count )
{
size_t next = i;
while ( next < count && strcmp(tokens[next], ",") )
next++;
const char* daemon = NULL;
const char* state = NULL;
for ( size_t n = i; n < next; n++ )
{
if ( !strncmp(tokens[n], "state=", strlen("state=")) )
state = tokens[n] + strlen("state=");
else if ( !strncmp(tokens[n], "daemon=", strlen("daemon=")) )
daemon = tokens[n] + strlen("daemon=");
}
if ( !state || !daemon )
errx(1, "missing information in reply");
pad(daemon, daemon_length + 2);
pad(state, state_length);
for ( size_t n = i; n < next; n++ )
{
if ( strncmp(tokens[n], "state=", strlen("state=")) &&
strncmp(tokens[n], "daemon=", strlen("daemon=")) )
printf(" %s", tokens[n]);
}
putchar('\n');
i = next;
if ( i < count && !strcmp(tokens[i], ",") )
i++;
}
}
int main(int argc, char* argv[])
{
const char* init_socket = getenv("INIT_SOCKET");
if ( !init_socket )
init_socket = "/var/run/init";
bool exit_code = false;
bool list = false;
bool no_await = false;
bool optional = true;
bool raw = false;
const char* source = "local";
const struct option longopts[] =
{
{"exit-code", no_argument, NULL, 256},
{"list", no_argument, NULL, 'l'},
{"no-await", no_argument, NULL, 257},
{"no-optional", no_argument, NULL, 258},
{"source", required_argument, NULL, 's'},
{"raw", no_argument, NULL, 'r'},
{0, 0, 0, 0}
};
const char* opts = "lrs:";
int opt;
while ( (opt = getopt_long(argc, argv, opts, longopts, NULL)) != -1 )
{
switch ( opt )
{
case 'l': list = true; break;
case 'r': raw = true; break;
case 's': source = optarg; break;
case 256: exit_code = true; break;
case 257: no_await = true; break;
case 258: optional = false; break;
default: return 2;
}
}
int fd = open_local_client_socket(init_socket, 0);
if ( fd < 0 )
err(1, "%s", init_socket);
FILE* fp = fdopen(fd, "r+");
if ( !fp )
err(1, "fdopen");
if ( raw )
{
for ( int i = optind; i < argc; i++ )
{
if ( fprintf(fp, "%s%c", argv[i], i + 1 == argc ? '\n' : ' ') < 0 )
err(1, "%s", init_socket);
}
size_t tokens_count;
char** tokens = receive(fp, &tokens_count);
for ( size_t i = 0; i < tokens_count; i++ )
printf("%s%c", tokens[i], i + 1 == tokens_count ? '\n' : ' ');
return 0;
}
char flags[sizeof(" optional no-await exit-code")];
snprintf(flags, sizeof(flags), "%s%s%s",
optional ? " optional" : "",
no_await ? " no-await" : "",
exit_code ? " exit-code" : "");
char* source_path;
if ( asprintf(&source_path, "/etc/init/%s", source) < 0 )
err(1, "malloc");
bool no_command = list;
if ( no_command && 0 < argc - optind )
errx(1, "unexpected extra operand: %s", argv[optind]);
else if ( !no_command && argc - optind < 2 )
errx(1, "usage: <daemon> <command>");
const char* daemon = !no_command ? argv[optind++] : NULL;
const char* command = !no_command ? argv[optind++] : NULL;
if ( command && strcmp(command, "signal") && 0 < argc - optind )
errx(1, "unexpected extra operand: %s", argv[optind]);
if ( list )
fprintf(fp, "list\n");
else if ( !strcmp(command, "enable") )
{
check_daemon_exists(daemon);
rewrite(source_path, daemon, flags);
fprintf(fp, "require %s %s %s start\n", source, daemon, flags);
}
else if ( !strcmp(command, "disable") )
{
rewrite(source_path, daemon, NULL);
fprintf(fp, "unrequire %s %s\n", source, daemon);
}
else if ( !strcmp(command, "start") )
fprintf(fp, "require %s %s %s start\n", source, daemon, flags);
else if ( !strcmp(command, "stop") )
fprintf(fp, "unrequire %s %s\n", source, daemon);
else if ( !strcmp(command, "restart") )
fprintf(fp, "restart %s\n", daemon);
else if ( !strcmp(command, "reload") )
fprintf(fp, "reload %s\n", daemon);
else if ( !strcmp(command, "reconfigure") )
fprintf(fp, "reconfigure %s\n", daemon);
else if ( !strcmp(command, "terminate") )
fprintf(fp, "terminate %s\n", daemon);
else if ( !strcmp(command, "kill") )
fprintf(fp, "kill %s\n", daemon);
else if ( !strcmp(command, "signal") )
{
if ( argc - optind < 1 )
errx(1, "expected signal name");
const char* signal = argv[optind++];
if ( 0 < argc - optind )
errx(1, "unexpected extra operand: %s", argv[optind]);
fprintf(fp, "signal %s %s\n", daemon, signal);
}
else if ( !strcmp(command, "status") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "state") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "pid") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "exit-code") )
fprintf(fp, "status %s\n", daemon);
else if ( !strcmp(command, "requirements") ||
!strcmp(command, "dependents") ||
!strcmp(command, "edges") )
fprintf(fp, "%s %s\n", command, daemon);
else
errx(1, "unknown command: %s", command);
if ( ferror(fp) || fflush(fp) == EOF )
err(1, "%s", init_socket);
size_t tokens_count;
char** tokens = receive(fp, &tokens_count);
if ( list )
format_statuses(tokens + 1, tokens_count - 1);
else if ( !strcmp(command, "status") )
format_statuses(tokens + 1, tokens_count - 1);
else if ( !strcmp(command, "state") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strncmp(tokens[i], "state=", strlen("state=")) )
{
puts(tokens[i] + strlen("state="));
break;
}
}
}
else if ( !strcmp(command, "pid") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strncmp(tokens[i], "pid=", strlen("pid=")) )
{
const char* arg = tokens[i] + strlen("pid=");
if ( strcmp(arg, "0") )
puts(arg);
break;
}
}
}
else if ( !strcmp(command, "exit-code") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strncmp(tokens[i], "exit=", strlen("exit=")) )
{
const char* arg = tokens[i] + strlen("exit=");
if ( strcmp(arg, "n/a") )
puts(arg);
break;
}
}
}
else if ( !strcmp(command, "requirements") ||
!strcmp(command, "dependents") ||
!strcmp(command, "edges") )
{
for ( size_t i = 1; i < tokens_count; i++ )
{
if ( !strcmp(tokens[i], ",") )
continue;
size_t eol = i + 1 == tokens_count || !strcmp(tokens[i + 1], ",");
printf("%s%c", tokens[i], eol ? '\n' : ' ');
}
}
return 0;
}

2
iso9660/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
iso9660fs
*.o

33
iso9660/Makefile Normal file
View file

@ -0,0 +1,33 @@
include ../build-aux/platform.mak
include ../build-aux/compiler.mak
include ../build-aux/version.mak
include ../build-aux/dirs.mak
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
CXXFLAGS?=$(OPTLEVEL)
CPPFLAGS:=$(CPPFLAGS) -DVERSIONSTR=\"$(VERSION)\"
CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti -fcheck-new
LIBS:=$(LIBS)
ifeq ($(HOST_IS_SORTIX),0)
LIBS:=$(LIBS) -lfuse
CPPFLAGS:=$(CPPFLAGS) -D_FILE_OFFSET_BITS=64
endif
BINARIES:=iso9660fs
all: $(BINARIES)
.PHONY: all install clean
install: all
mkdir -p $(DESTDIR)$(SBINDIR)
install $(BINARIES) $(DESTDIR)$(SBINDIR)
iso9660fs: *.cpp *.h
$(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) *.cpp -o $@ $(LIBS)
clean:
rm -f $(BINARIES) *.o

108
iso9660/block.cpp Normal file
View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* block.cpp
* Blocks in the filesystem.
*/
#include <sys/types.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include "block.h"
#include "device.h"
#include "ioleast.h"
Block::Block()
{
this->block_data = NULL;
}
Block::Block(Device* device, uint32_t block_id)
{
Construct(device, block_id);
}
void Block::Construct(Device* device, uint32_t block_id)
{
this->prev_block = NULL;
this->next_block = NULL;
this->prev_hashed = NULL;
this->next_hashed = NULL;
this->device = device;
this->reference_count = 1;
this->block_id = block_id;
}
Block::~Block()
{
Destruct();
delete[] block_data;
}
void Block::Destruct()
{
Unlink();
}
void Block::Refer()
{
reference_count++;
}
void Block::Unref()
{
if ( !--reference_count )
{
#if 0
device->block_count--;
delete this;
#endif
}
}
void Block::Use()
{
Unlink();
Prelink();
}
void Block::Unlink()
{
(prev_block ? prev_block->next_block : device->mru_block) = next_block;
(next_block ? next_block->prev_block : device->lru_block) = prev_block;
size_t bin = block_id % DEVICE_HASH_LENGTH;
(prev_hashed ? prev_hashed->next_hashed : device->hash_blocks[bin]) = next_hashed;
if ( next_hashed ) next_hashed->prev_hashed = prev_hashed;
}
void Block::Prelink()
{
prev_block = NULL;
next_block = device->mru_block;
if ( device->mru_block )
device->mru_block->prev_block = this;
device->mru_block = this;
if ( !device->lru_block )
device->lru_block = this;
size_t bin = block_id % DEVICE_HASH_LENGTH;
prev_hashed = NULL;
next_hashed = device->hash_blocks[bin];
device->hash_blocks[bin] = this;
if ( next_hashed )
next_hashed->prev_hashed = this;
}

53
iso9660/block.h Normal file
View file

@ -0,0 +1,53 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* block.h
* Blocks in the filesystem.
*/
#ifndef BLOCK_H
#define BLOCK_H
class Device;
class Block
{
public:
Block();
Block(Device* device, uint32_t block_id);
~Block();
void Construct(Device* device, uint32_t block_id);
void Destruct();
public:
Block* prev_block;
Block* next_block;
Block* prev_hashed;
Block* next_hashed;
Device* device;
size_t reference_count;
uint32_t block_id;
uint8_t* block_data;
public:
void Refer();
void Unref();
void Use();
void Unlink();
void Prelink();
};
#endif

107
iso9660/device.cpp Normal file
View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* device.cpp
* Block device.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include "block.h"
#include "device.h"
#include "ioleast.h"
Device::Device(int fd, const char* path, uint32_t block_size)
{
this->mru_block = NULL;
this->lru_block = NULL;
for ( size_t i = 0; i < DEVICE_HASH_LENGTH; i++ )
hash_blocks[i] = NULL;
struct stat st;
fstat(fd, &st);
this->device_size = st.st_size;
this->path = path;
this->block_size = block_size;
this->fd = fd;
this->block_count = 0;
#ifdef __sortix__
// TODO: This isn't scaleable if there's multiple filesystems mounted.
size_t memory;
memstat(NULL, &memory);
this->block_limit = (memory / 10) / block_size;
#else
this->block_limit = 32768;
#endif
}
Device::~Device()
{
while ( mru_block )
delete mru_block;
close(fd);
}
Block* Device::AllocateBlock()
{
if ( block_limit <= block_count )
{
for ( Block* block = lru_block; block; block = block->prev_block )
{
if ( block->reference_count )
continue;
block->Destruct(); // Syncs.
return block;
}
}
uint8_t* data = new uint8_t[block_size];
if ( !data ) // TODO: Use operator new nothrow!
return NULL;
Block* block = new Block();
if ( !block ) // TODO: Use operator new nothrow!
return delete[] data, (Block*) NULL;
block->block_data = data;
block_count++;
return block;
}
Block* Device::GetBlock(uint32_t block_id)
{
if ( Block* block = GetCachedBlock(block_id) )
return block;
Block* block = AllocateBlock();
if ( !block )
return NULL;
block->Construct(this, block_id);
off_t file_offset = (off_t) block_size * (off_t) block_id;
preadall(fd, block->block_data, block_size, file_offset);
block->Prelink();
return block;
}
Block* Device::GetCachedBlock(uint32_t block_id)
{
size_t bin = block_id % DEVICE_HASH_LENGTH;
for ( Block* iter = hash_blocks[bin]; iter; iter = iter->next_hashed )
if ( iter->block_id == block_id )
return iter->Refer(), iter;
return NULL;
}

51
iso9660/device.h Normal file
View file

@ -0,0 +1,51 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* device.h
* Block device.
*/
#ifndef DEVICE_H
#define DEVICE_H
class Block;
static const size_t DEVICE_HASH_LENGTH = 1 << 16;
class Device
{
public:
Device(int fd, const char* path, uint32_t block_size);
~Device();
public:
Block* mru_block;
Block* lru_block;
Block* hash_blocks[DEVICE_HASH_LENGTH];
off_t device_size;
const char* path;
uint32_t block_size;
int fd;
size_t block_count;
size_t block_limit;
public:
Block* AllocateBlock();
Block* GetBlock(uint32_t block_id);
Block* GetCachedBlock(uint32_t block_id);
};
#endif

87
iso9660/filesystem.cpp Normal file
View file

@ -0,0 +1,87 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* filesystem.cpp
* ISO 9660 filesystem implementation.
*/
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <stdio.h> // DEBUG
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "inode.h"
#include "util.h"
Filesystem::Filesystem(Device* device, const char* mount_path,
uint64_t pvd_offset)
{
// TODO: This should be replaced by the . entry within the root directory
// so the Rock Ridge extensions are available.
uint32_t pvd_block_id = pvd_offset / device->block_size;
this->pvd_block = device->GetBlock(pvd_block_id);
assert(pvd_block); // TODO: This can fail.
this->pvd = (struct iso9660_pvd*)
(pvd_block->block_data + pvd_offset % device->block_size);
this->root_ino = pvd_offset + offsetof(struct iso9660_pvd, root_dirent);
this->device = device;
this->mount_path = mount_path;
this->block_size = device->block_size;
this->mru_inode = NULL;
this->lru_inode = NULL;
for ( size_t i = 0; i < INODE_HASH_LENGTH; i++ )
this->hash_inodes[i] = NULL;
}
Filesystem::~Filesystem()
{
while ( mru_inode )
delete mru_inode;
pvd_block->Unref();
}
Inode* Filesystem::GetInode(iso9660_ino_t inode_id)
{
size_t bin = inode_id % INODE_HASH_LENGTH;
for ( Inode* iter = hash_inodes[bin]; iter; iter = iter->next_hashed )
if ( iter->inode_id == inode_id )
return iter->Refer(), iter;
uint32_t block_id = inode_id / block_size;
uint32_t offset = inode_id % block_size;
Block* block = device->GetBlock(block_id);
if ( !block )
return (Inode*) NULL;
Inode* inode = new Inode(this, inode_id);
if ( !inode )
return block->Unref(), (Inode*) NULL;
inode->data_block = block;
uint8_t* buf = inode->data_block->block_data + offset;
inode->data = (struct iso9660_dirent*) buf;
inode->Prelink();
inode->Parse();
return inode;
}

52
iso9660/filesystem.h Normal file
View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* filesystem.h
* ISO 9660 filesystem implementation.
*/
#ifndef FILESYSTEM_H
#define FILESYSTEM_H
#include "iso9660.h"
class Device;
class Inode;
static const size_t INODE_HASH_LENGTH = 1 << 16;
class Filesystem
{
public:
Filesystem(Device* device, const char* mount_path, uint64_t pvd_offset);
~Filesystem();
public:
Block* pvd_block;
struct iso9660_pvd* pvd;
Device* device;
const char* mount_path;
iso9660_ino_t root_ino;
uint32_t block_size;
Inode* mru_inode;
Inode* lru_inode;
Inode* hash_inodes[INODE_HASH_LENGTH];
public:
Inode* GetInode(iso9660_ino_t inode_id);
};
#endif

701
iso9660/fsmarshall.cpp Normal file
View file

@ -0,0 +1,701 @@
/*
* Copyright (c) 2013, 2014, 2015, 2016, 2022, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fsmarshall.cpp
* Sortix fsmarshall frontend.
*/
#if defined(__sortix__)
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <dirent.h>
#include <fcntl.h>
#include <ioleast.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <timespec.h>
#include <unistd.h>
#include <sortix/dirent.h>
#include <fsmarshall.h>
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "fsmarshall.h"
#include "fuse.h"
#include "inode.h"
#include "iso9660fs.h"
bool RespondData(int chl, const void* ptr, size_t count)
{
return writeall(chl, ptr, count) == count;
}
bool RespondHeader(int chl, size_t type, size_t size)
{
struct fsm_msg_header hdr;
hdr.msgtype = type;
hdr.msgsize = size;
return RespondData(chl, &hdr, sizeof(hdr));
}
bool RespondMessage(int chl, size_t type, const void* ptr, size_t count)
{
return RespondHeader(chl, type, count) &&
RespondData(chl, ptr, count);
}
bool RespondError(int chl, int errnum)
{
struct fsm_resp_error body;
body.errnum = errnum;
return RespondMessage(chl, FSM_RESP_ERROR, &body, sizeof(body));
}
bool RespondSuccess(int chl)
{
struct fsm_resp_success body;
return RespondMessage(chl, FSM_RESP_SUCCESS, &body, sizeof(body));
}
bool RespondStat(int chl, struct stat* st)
{
struct fsm_resp_stat body;
body.st = *st;
return RespondMessage(chl, FSM_RESP_STAT, &body, sizeof(body));
}
bool RespondStatVFS(int chl, struct statvfs* stvfs)
{
struct fsm_resp_statvfs body;
body.stvfs = *stvfs;
return RespondMessage(chl, FSM_RESP_STATVFS, &body, sizeof(body));
}
bool RespondSeek(int chl, off_t offset)
{
struct fsm_resp_lseek body;
body.offset = offset;
return RespondMessage(chl, FSM_RESP_LSEEK, &body, sizeof(body));
}
bool RespondRead(int chl, const uint8_t* buf, size_t count)
{
struct fsm_resp_read body;
body.count = count;
return RespondMessage(chl, FSM_RESP_READ, &body, sizeof(body)) &&
RespondData(chl, buf, count);
}
bool RespondReadlink(int chl, const uint8_t* buf, size_t count)
{
struct fsm_resp_readlink body;
body.targetlen = count;
return RespondMessage(chl, FSM_RESP_READLINK, &body, sizeof(body)) &&
RespondData(chl, buf, count);
}
bool RespondWrite(int chl, size_t count)
{
struct fsm_resp_write body;
body.count = count;
return RespondMessage(chl, FSM_RESP_WRITE, &body, sizeof(body));
}
bool RespondOpen(int chl, ino_t ino, mode_t type)
{
struct fsm_resp_open body;
body.ino = ino;
body.type = type;
return RespondMessage(chl, FSM_RESP_OPEN, &body, sizeof(body));
}
bool RespondMakeDir(int chl, ino_t ino)
{
struct fsm_resp_mkdir body;
body.ino = ino;
return RespondMessage(chl, FSM_RESP_MKDIR, &body, sizeof(body));
}
bool RespondReadDir(int chl, struct dirent* dirent)
{
struct fsm_resp_readdirents body;
body.ino = dirent->d_ino;
body.type = dirent->d_type;
body.namelen = dirent->d_namlen;
return RespondMessage(chl, FSM_RESP_READDIRENTS, &body, sizeof(body)) &&
RespondData(chl, dirent->d_name, dirent->d_namlen);
}
bool RespondTCGetBlob(int chl, const void* data, size_t data_size)
{
struct fsm_resp_tcgetblob body;
body.count = data_size;
return RespondMessage(chl, FSM_RESP_TCGETBLOB, &body, sizeof(body)) &&
RespondData(chl, data, data_size);
}
Inode* SafeGetInode(Filesystem* fs, ino_t ino)
{
if ( (iso9660_ino_t) ino != ino )
return errno = EBADF, (Inode*) ino;
return fs->GetInode((iso9660_ino_t) ino);
}
void HandleRefer(int chl, struct fsm_req_refer* msg, Filesystem* fs)
{
(void) chl;
if ( Inode* inode = SafeGetInode(fs, msg->ino) )
{
inode->RemoteRefer();
inode->Unref();
}
}
void HandleUnref(int chl, struct fsm_req_unref* msg, Filesystem* fs)
{
(void) chl;
if ( Inode* inode = SafeGetInode(fs, msg->ino) )
{
inode->RemoteUnref();
inode->Unref();
}
}
void HandleSync(int chl, struct fsm_req_sync* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
inode->Unref();
RespondSuccess(chl);
}
void HandleStat(int chl, struct fsm_req_stat* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
struct stat st;
StatInode(inode, &st);
inode->Unref();
RespondStat(chl, &st);
}
void HandleChangeMode(int chl, struct fsm_req_chmod* /*msg*/, Filesystem* /*fs*/)
{
RespondError(chl, EROFS);
}
void HandleChangeOwner(int chl, struct fsm_req_chown* /*msg*/, Filesystem* /*fs*/)
{
RespondError(chl, EROFS);
}
void HandleUTimens(int chl, struct fsm_req_utimens* /*msg*/, Filesystem* /*fs*/)
{
RespondError(chl, EROFS);
}
void HandleTruncate(int chl, struct fsm_req_truncate* /*msg*/, Filesystem* /*fs*/)
{
RespondError(chl, EROFS);
}
void HandleSeek(int chl, struct fsm_req_lseek* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( msg->whence == SEEK_SET )
RespondSeek(chl, msg->offset);
else if ( msg->whence == SEEK_END )
{
off_t inode_size = inode->Size();
if ( (msg->offset < 0 && inode_size + msg->offset < 0) ||
(0 <= msg->offset && OFF_MAX - inode_size < msg->offset) )
RespondError(chl, EOVERFLOW);
else
RespondSeek(chl, msg->offset + inode_size);
}
else
RespondError(chl, EINVAL);
inode->Unref();
}
void HandleReadAt(int chl, struct fsm_req_pread* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
uint8_t* buf = (uint8_t*) malloc(msg->count);
if ( !buf ) { inode->Unref(); RespondError(chl, errno); return; }
ssize_t amount = inode->ReadAt(buf, msg->count, msg->offset);
inode->Unref();
if ( amount < 0 ) { free(buf); RespondError(chl, errno); return; }
RespondRead(chl, buf, amount);
free(buf);
}
void HandleWriteAt(int chl, struct fsm_req_pwrite* /*msg*/, Filesystem* /*fs*/)
{
RespondError(chl, EROFS);
}
void HandleOpen(int chl, struct fsm_req_open* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
Inode* result = inode->Open(path, msg->flags, FsModeFromHostMode(msg->mode));
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondOpen(chl, result->inode_id, result->Mode() & S_IFMT);
result->Unref();
}
void HandleMakeDir(int chl, struct fsm_req_mkdir* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
inode->Unref();
RespondError(chl, EROFS);
}
void HandleReadDir(int chl, struct fsm_req_readdirents* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( !S_ISDIR(inode->Mode()) )
{
inode->Unref();
RespondError(chl, ENOTDIR);
return;
}
union
{
struct dirent kernel_entry;
uint8_t padding[sizeof(struct dirent) + 256];
};
memset(&kernel_entry, 0, sizeof(kernel_entry));
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( inode->ReadDirectory(&offset, &block, &block_id,
msg->rec_num ? NULL : name, &file_type,
&inode_id) )
{
if ( !(msg->rec_num--) )
{
size_t name_len = strlen(name);
kernel_entry.d_reclen = sizeof(kernel_entry) + name_len;
kernel_entry.d_ino = inode_id;
kernel_entry.d_dev = 0;
kernel_entry.d_type = HostDTFromFsDT(file_type);
kernel_entry.d_namlen = name_len;
memcpy(kernel_entry.d_name, name, name_len);
size_t dname_offset = offsetof(struct dirent, d_name);
padding[dname_offset + kernel_entry.d_namlen] = '\0';
block->Unref();
inode->Unref();
RespondReadDir(chl, &kernel_entry);
return;
}
}
int errnum = errno;
if ( block )
block->Unref();
inode->Unref();
if ( errnum )
{
RespondError(chl, errnum);
return;
}
kernel_entry.d_reclen = sizeof(kernel_entry);
RespondReadDir(chl, &kernel_entry);
}
void HandleIsATTY(int chl, struct fsm_req_isatty* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
RespondError(chl, ENOTTY);
inode->Unref();
}
void HandleUnlink(int chl, struct fsm_req_unlink* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->Unlink(path, false);
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleRemoveDir(int chl, struct fsm_req_rmdir* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->RemoveDirectory(path);
free(path);
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleLink(int chl, struct fsm_req_link* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
Inode* dest = SafeGetInode(fs, msg->linkino);
if ( !dest ) { inode->Unref(); RespondError(chl, errno); return; }
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->namelen+1);
if ( !path )
{
RespondError(chl, errno);
inode->Unref();
return;
}
memcpy(path, pathraw, msg->namelen);
path[msg->namelen] = '\0';
bool result = inode->Link(path, dest);
free(path);
dest->Unref();
inode->Unref();
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleSymlink(int chl, struct fsm_req_symlink* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->dirino);
if ( !inode ) { RespondError(chl, errno); return; }
inode->Unref();
RespondError(chl, EROFS);
}
void HandleReadlink(int chl, struct fsm_req_readlink* msg, Filesystem* fs)
{
Inode* inode = SafeGetInode(fs, msg->ino);
if ( !inode ) { RespondError(chl, errno); return; }
if ( !ISO9660_S_ISLNK(inode->Mode()) ) { inode->Unref(); RespondError(chl, EINVAL); return; }
size_t count = inode->Size();
uint8_t* buf = (uint8_t*) malloc(count);
if ( !buf ) { inode->Unref(); RespondError(chl, errno); return; }
ssize_t amount = inode->ReadLink(buf, count);
inode->Unref();
if ( amount < 0 ) { RespondError(chl, errno); return; }
RespondReadlink(chl, buf, amount);
free(buf);
}
void HandleRename(int chl, struct fsm_req_rename* msg, Filesystem* fs)
{
char* pathraw = (char*) &(msg[1]);
char* path = (char*) malloc(msg->oldnamelen+1 + msg->newnamelen+1);
if ( !path ) { RespondError(chl, errno); return; }
memcpy(path, pathraw, msg->oldnamelen);
path[msg->oldnamelen] = '\0';
memcpy(path + msg->oldnamelen + 1, pathraw + msg->oldnamelen, msg->newnamelen);
path[msg->oldnamelen + 1 + msg->newnamelen] = '\0';
const char* oldname = path;
const char* newname = path + msg->oldnamelen + 1;
Inode* olddir = SafeGetInode(fs, msg->olddirino);
if ( !olddir ) { free(path); RespondError(chl, errno); return; }
Inode* newdir = SafeGetInode(fs, msg->newdirino);
if ( !newdir ) { olddir->Unref(); free(path); RespondError(chl, errno); return; }
bool result = newdir->Rename(olddir, oldname, newname);
newdir->Unref();
olddir->Unref();
free(path);
if ( !result ) { RespondError(chl, errno); return; }
RespondSuccess(chl);
}
void HandleStatVFS(int chl, struct fsm_req_statvfs* msg, Filesystem* fs)
{
(void) msg;
struct statvfs stvfs;
stvfs.f_bsize = fs->block_size;
stvfs.f_frsize = fs->block_size;
stvfs.f_blocks = fs->device->device_size / fs->block_size;
stvfs.f_bfree = 0;
stvfs.f_bavail = 0;
stvfs.f_files = 0;
stvfs.f_ffree = 0;
stvfs.f_favail = 0;
stvfs.f_ffree = 0;
stvfs.f_fsid = 0;
stvfs.f_flag = ST_RDONLY;
stvfs.f_namemax = 255;
RespondStatVFS(chl, &stvfs);
}
void HandleTCGetBlob(int chl, struct fsm_req_tcgetblob* msg, Filesystem* fs)
{
char* nameraw = (char*) &(msg[1]);
char* name = (char*) malloc(msg->namelen + 1);
if ( !name )
return (void) RespondError(chl, errno);
memcpy(name, nameraw, msg->namelen);
name[msg->namelen] = '\0';
//static const char index[] = "device-path\0filesystem-type\0filesystem-uuid\0mount-path\0";
static const char index[] = "device-path\0filesystem-type\0mount-path\0";
if ( !strcmp(name, "") )
RespondTCGetBlob(chl, index, sizeof(index) - 1);
else if ( !strcmp(name, "device-path") )
RespondTCGetBlob(chl, fs->device->path, strlen(fs->device->path));
else if ( !strcmp(name, "filesystem-type") )
RespondTCGetBlob(chl, "iso9660", strlen("iso9660"));
// TODO: Some kind of unique id.
//else if ( !strcmp(name, "filesystem-uuid") )
// RespondTCGetBlob(chl, fs->sb->s_uuid, sizeof(fs->sb->s_uuid));
else if ( !strcmp(name, "mount-path") )
RespondTCGetBlob(chl, fs->mount_path, strlen(fs->mount_path));
else
RespondError(chl, ENOENT);
free(name);
}
void HandleIncomingMessage(int chl, struct fsm_msg_header* hdr, Filesystem* fs)
{
request_uid = hdr->uid;
request_gid = hdr->gid;
if ( (uint16_t) request_uid != request_uid ||
(uint16_t) request_gid != request_gid )
{
fprintf(stderr, "extfs: id exceeded 16-bit: uid=%ju gid=%ju\n",
(uintmax_t) request_uid, (uintmax_t) request_gid);
RespondError(chl, EOVERFLOW);
return;
}
typedef void (*handler_t)(int, void*, Filesystem*);
handler_t handlers[FSM_MSG_NUM] = { NULL };
handlers[FSM_REQ_SYNC] = (handler_t) HandleSync;
handlers[FSM_REQ_STAT] = (handler_t) HandleStat;
handlers[FSM_REQ_CHMOD] = (handler_t) HandleChangeMode;
handlers[FSM_REQ_CHOWN] = (handler_t) HandleChangeOwner;
handlers[FSM_REQ_TRUNCATE] = (handler_t) HandleTruncate;
handlers[FSM_REQ_LSEEK] = (handler_t) HandleSeek;
handlers[FSM_REQ_PREAD] = (handler_t) HandleReadAt;
handlers[FSM_REQ_OPEN] = (handler_t) HandleOpen;
handlers[FSM_REQ_READDIRENTS] = (handler_t) HandleReadDir;
handlers[FSM_REQ_PWRITE] = (handler_t) HandleWriteAt;
handlers[FSM_REQ_ISATTY] = (handler_t) HandleIsATTY;
handlers[FSM_REQ_UTIMENS] = (handler_t) HandleUTimens;
handlers[FSM_REQ_MKDIR] = (handler_t) HandleMakeDir;
handlers[FSM_REQ_RMDIR] = (handler_t) HandleRemoveDir;
handlers[FSM_REQ_UNLINK] = (handler_t) HandleUnlink;
handlers[FSM_REQ_LINK] = (handler_t) HandleLink;
handlers[FSM_REQ_SYMLINK] = (handler_t) HandleSymlink;
handlers[FSM_REQ_READLINK] = (handler_t) HandleReadlink;
handlers[FSM_REQ_RENAME] = (handler_t) HandleRename;
handlers[FSM_REQ_REFER] = (handler_t) HandleRefer;
handlers[FSM_REQ_UNREF] = (handler_t) HandleUnref;
handlers[FSM_REQ_STATVFS] = (handler_t) HandleStatVFS;
handlers[FSM_REQ_TCGETBLOB] = (handler_t) HandleTCGetBlob;
if ( FSM_MSG_NUM <= hdr->msgtype || !handlers[hdr->msgtype] )
{
fprintf(stderr, "extfs: message type %zu not supported\n", hdr->msgtype);
RespondError(chl, ENOTSUP);
return;
}
uint8_t body_buffer[65536];
uint8_t* body = body_buffer;
if ( sizeof(body_buffer) < hdr->msgsize )
{
body = (uint8_t*) mmap(NULL, hdr->msgsize, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ( (void*) body == MAP_FAILED )
{
RespondError(chl, errno);
return;
}
}
if ( readall(chl, body, hdr->msgsize) == hdr->msgsize )
handlers[hdr->msgtype](chl, body, fs);
else
RespondError(chl, errno);
if ( sizeof(body_buffer) < hdr->msgsize )
munmap(body, hdr->msgsize);
}
static volatile bool should_terminate = false;
void TerminationHandler(int)
{
should_terminate = true;
}
static void ready(void)
{
const char* readyfd_env = getenv("READYFD");
if ( !readyfd_env )
return;
int readyfd = atoi(readyfd_env);
char c = '\n';
write(readyfd, &c, 1);
close(readyfd);
unsetenv("READYFD");
}
int fsmarshall_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev)
{
(void) argv0;
// Stat the root inode.
struct stat root_inode_st;
Inode* root_inode = fs->GetInode(fs->root_ino);
if ( !root_inode )
err(1, "GetInode");
StatInode(root_inode, &root_inode_st);
root_inode->Unref();
// Create a filesystem server connected to the kernel that we'll listen on.
int serverfd = fsm_mountat(AT_FDCWD, mount_path, &root_inode_st, 0);
if ( serverfd < 0 )
err(1, "%s", mount_path);
// Make sure the server isn't unexpectedly killed and data is lost.
signal(SIGINT, TerminationHandler);
signal(SIGTERM, TerminationHandler);
signal(SIGQUIT, TerminationHandler);
// Become a background process in its own process group by default.
if ( !foreground )
{
pid_t child_pid = fork();
if ( child_pid < 0 )
err(1, "fork");
if ( child_pid )
exit(0);
setpgid(0, 0);
}
else
ready();
// Listen for filesystem messages.
int channel;
while ( 0 <= (channel = accept(serverfd, NULL, NULL)) )
{
if ( should_terminate )
break;
struct fsm_msg_header hdr;
size_t amount;
if ( (amount = readall(channel, &hdr, sizeof(hdr))) != sizeof(hdr) )
{
//warn("incomplete header: got %zi of %zu bytes", amount, sizeof(hdr));
errno = 0;
continue;
}
HandleIncomingMessage(channel, &hdr, fs);
close(channel);
}
// Garbage collect all open inode references.
while ( fs->mru_inode )
{
Inode* inode = fs->mru_inode;
if ( inode->remote_reference_count )
inode->RemoteUnref();
else if ( inode->reference_count )
inode->Unref();
}
close(serverfd);
delete fs;
delete dev;
return 0;
}
#endif

31
iso9660/fsmarshall.h Normal file
View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fsmarshall.h
* Sortix fsmarshall frontend.
*/
#ifndef FSMARSHALL_H
#define FSMARSHALL_H
class Device;
class Filesystem;
int fsmarshall_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev);
#endif

572
iso9660/fuse.cpp Normal file
View file

@ -0,0 +1,572 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fuse.cpp
* FUSE frontend.
*/
#if !defined(__sortix__)
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define FUSE_USE_VERSION 26
#include <fuse.h>
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "fuse.h"
#include "inode.h"
#include "iso9660fs.h"
#include <stdio.h> // DEBUG
struct iso9660_fuse_ctx
{
Device* dev;
Filesystem* fs;
};
#ifndef S_SETABLE
#define S_SETABLE 02777
#endif
#define FUSE_FS (((struct iso9660_fuse_ctx*) (fuse_get_context()->private_data))->fs)
void* iso9660_fuse_init(struct fuse_conn_info* /*conn*/)
{
return fuse_get_context()->private_data;
}
void iso9660_fuse_destroy(void* fs_private)
{
struct iso9660_fuse_ctx* iso9660_fuse_ctx =
(struct iso9660_fuse_ctx*) fs_private;
while ( iso9660_fuse_ctx->fs->mru_inode )
{
Inode* inode = iso9660_fuse_ctx->fs->mru_inode;
if ( inode->remote_reference_count )
inode->RemoteUnref();
else if ( inode->reference_count )
inode->Unref();
}
delete iso9660_fuse_ctx->fs; iso9660_fuse_ctx->fs = NULL;
delete iso9660_fuse_ctx->dev; iso9660_fuse_ctx->dev = NULL;
}
Inode* iso9660_fuse_resolve_path(const char* path)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode(fs->root_ino);
if ( !inode )
return (Inode*) NULL;
while ( path[0] )
{
if ( *path == '/' )
{
if ( !ISO9660_S_ISDIR(inode->Mode()) )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path++;
continue;
}
size_t elem_len = strcspn(path, "/");
char* elem = strndup(path, elem_len);
if ( !elem )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path += elem_len;
Inode* next = inode->Open(elem, O_RDONLY, 0);
free(elem);
inode->Unref();
if ( !next )
return NULL;
inode = next;
}
return inode;
}
// Assumes that the path doesn't end with / unless it's the root directory.
Inode* iso9660_fuse_parent_dir(const char** path_ptr)
{
const char* path = *path_ptr;
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode(fs->root_ino);
if ( !inode )
return (Inode*) NULL;
while ( strchr(path, '/') )
{
if ( *path == '/' )
{
if ( !ISO9660_S_ISDIR(inode->Mode()) )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path++;
continue;
}
size_t elem_len = strcspn(path, "/");
char* elem = strndup(path, elem_len);
if ( !elem )
return inode->Unref(), errno = ENOTDIR, (Inode*) NULL;
path += elem_len;
Inode* next = inode->Open(elem, O_RDONLY, 0);
free(elem);
inode->Unref();
if ( !next )
return (Inode*) NULL;
inode = next;
}
*path_ptr = *path ? path : ".";
assert(!strchr(*path_ptr, '/'));
return inode;
}
int iso9660_fuse_getattr(const char* path, struct stat* st)
{
Inode* inode = iso9660_fuse_resolve_path(path);
if ( !inode )
return -errno;
StatInode(inode, st);
inode->Unref();
return 0;
}
int iso9660_fuse_fgetattr(const char* /*path*/, struct stat* st,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
StatInode(inode, st);
inode->Unref();
return 0;
}
int iso9660_fuse_readlink(const char* path, char* buf, size_t bufsize)
{
Inode* inode = iso9660_fuse_resolve_path(path);
if ( !inode )
return -errno;
if ( !ISO9660_S_ISLNK(inode->Mode()) )
return inode->Unref(), -(errno = EINVAL);
if ( !bufsize )
return inode->Unref(), -(errno = EINVAL);
ssize_t amount = inode->ReadLink((uint8_t*) buf, bufsize);
if ( amount < 0 )
return inode->Unref(), -errno;
buf[(size_t) amount < bufsize ? (size_t) amount : bufsize - 1] = '\0';
inode->Unref();
return 0;
}
int iso9660_fuse_mknod(const char* path, mode_t mode, dev_t dev)
{
(void) path;
(void) mode;
(void) dev;
return -(errno = ENOSYS);
}
int iso9660_fuse_mkdir(const char* path, mode_t /*mode*/)
{
Inode* inode = iso9660_fuse_parent_dir(&path);
if ( !inode )
return -errno;
inode->Unref();
return -(errno = EROFS);
}
int iso9660_fuse_unlink(const char* path)
{
Inode* inode = iso9660_fuse_parent_dir(&path);
if ( !inode )
return -errno;
bool success = inode->Unlink(path, false);
inode->Unref();
return success ? 0 : -errno;
}
int iso9660_fuse_rmdir(const char* path)
{
Inode* inode = iso9660_fuse_parent_dir(&path);
if ( !inode )
return -errno;
bool success = inode->RemoveDirectory(path);
inode->Unref();
return success ? 0 : -errno;
}
int iso9660_fuse_symlink(const char* /*oldname*/, const char* newname)
{
Inode* newdir = iso9660_fuse_parent_dir(&newname);
if ( !newdir )
return -errno;
newdir->Unref();
return -(errno = EROFS);
}
int iso9660_fuse_rename(const char* oldname, const char* newname)
{
Inode* olddir = iso9660_fuse_parent_dir(&oldname);
if ( !olddir )
return -errno;
Inode* newdir = iso9660_fuse_parent_dir(&newname);
if ( !newdir )
return olddir->Unref(), -errno;
bool success = newdir->Rename(olddir, oldname, newname);
newdir->Unref();
olddir->Unref();
return success ? 0 : -errno;
}
int iso9660_fuse_link(const char* oldname, const char* newname)
{
Inode* inode = iso9660_fuse_resolve_path(oldname);
if ( !inode )
return -errno;
Inode* newdir = iso9660_fuse_parent_dir(&newname);
if ( !newdir )
return inode->Unref(), -errno;
bool success = inode->Link(newname, inode);
newdir->Unref();
inode->Unref();
return success ? 0 : -errno;
}
int iso9660_fuse_chmod(const char* path, mode_t mode)
{
Inode* inode = iso9660_fuse_resolve_path(path);
if ( !inode )
return -errno;
(void) mode;
return inode->Unref(), -(errno = EROFS);
}
int iso9660_fuse_chown(const char* path, uid_t owner, gid_t group)
{
Inode* inode = iso9660_fuse_resolve_path(path);
if ( !inode )
return -errno;
(void) owner;
(void) group;
return inode->Unref(), -(errno = EROFS);
}
int iso9660_fuse_truncate(const char* path, off_t size)
{
Inode* inode = iso9660_fuse_resolve_path(path);
if ( !inode )
return -errno;
(void) size;
return inode->Unref(), -(errno = EROFS);
}
int iso9660_fuse_ftruncate(const char* /*path*/, off_t size,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
(void) size;
return inode->Unref(), -(errno = EROFS);
}
int iso9660_fuse_open(const char* path, struct fuse_file_info* fi)
{
int flags = fi->flags;
Inode* dir = iso9660_fuse_parent_dir(&path);
if ( !dir )
return -errno;
Inode* result = dir->Open(path, flags, 0);
dir->Unref();
if ( !result )
return -errno;
fi->fh = (uint64_t) result->inode_id;
fi->keep_cache = 1;
result->RemoteRefer();
result->Unref();
return 0;
}
int iso9660_fuse_access(const char* path, int mode)
{
Inode* dir = iso9660_fuse_parent_dir(&path);
if ( !dir )
return -errno;
Inode* result = dir->Open(path, O_RDONLY, 0);
dir->Unref();
if ( !result )
return -errno;
(void) mode;
result->Unref();
return 0;
}
int iso9660_fuse_create(const char* path, mode_t mode, struct fuse_file_info* fi)
{
int flags = fi->flags | O_CREAT;
Inode* inode = iso9660_fuse_parent_dir(&path);
if ( !inode )
return -errno;
Inode* result = inode->Open(path, flags, FsModeFromHostMode(mode));
inode->Unref();
if ( !result )
return -errno;
fi->fh = (uint64_t) result->inode_id;
fi->keep_cache = 1;
result->RemoteRefer();
result->Unref();
return 0;
}
int iso9660_fuse_opendir(const char* path, struct fuse_file_info* fi)
{
return iso9660_fuse_open(path, fi);
}
int iso9660_fuse_read(const char* /*path*/, char* buf, size_t count,
off_t offset, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
if ( INT_MAX < count )
count = INT_MAX;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
ssize_t result = inode->ReadAt((uint8_t*) buf, count, offset);
inode->Unref();
return 0 <= result ? (int) result : -errno;
}
int iso9660_fuse_write(const char* /*path*/, const char* /*buf*/,
size_t /*count*/, off_t /*offset*/,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->Unref();
return -(errno = EROFS);
}
int iso9660_fuse_statfs(const char* /*path*/, struct statvfs* stvfs)
{
memset(stvfs, 0, sizeof(*stvfs));
Filesystem* fs = FUSE_FS;
stvfs->f_bsize = fs->block_size;
stvfs->f_frsize = fs->block_size;
stvfs->f_blocks = fs->device->device_size / fs->block_size;
stvfs->f_bfree = 0;
stvfs->f_bavail = 0;
stvfs->f_files = 0;
stvfs->f_ffree = 0;
stvfs->f_favail = 0;
stvfs->f_ffree = 0;
stvfs->f_fsid = 0;
stvfs->f_flag = ST_RDONLY;
stvfs->f_namemax = 255;
return 0;
}
int iso9660_fuse_flush(const char* /*path*/, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->Unref();
return 0;
}
int iso9660_fuse_release(const char* /*path*/, struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->RemoteUnref();
inode->Unref();
return 0;
}
int iso9660_fuse_releasedir(const char* path, struct fuse_file_info* fi)
{
return iso9660_fuse_release(path, fi);
}
int iso9660_fuse_fsync(const char* /*path*/, int data, struct fuse_file_info* fi)
{
(void) data;
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((uint32_t) fi->fh);
if ( !inode )
return -errno;
inode->Unref();
return 0;
}
/*int iso9660_fuse_syncdir(const char* path, int data, struct fuse_file_info* fi)
{
return iso9660_fuse_sync(path, data, fi);
}*/
/*int iso9660_fuse_setxattr(const char *, const char *, const char *, size_t, int)
{
return -(errno = ENOSYS);
}*/
/*int iso9660_fuse_getxattr(const char *, const char *, char *, size_t)
{
return -(errno = ENOSYS);
}*/
/*int iso9660_fuse_listxattr(const char *, char *, size_t)
{
return -(errno = ENOSYS);
}*/
/*int iso9660_fuse_removexattr(const char *, const char *)
{
return -(errno = ENOSYS);
}*/
int iso9660_fuse_readdir(const char* /*path*/, void* buf,
fuse_fill_dir_t filler, off_t rec_num,
struct fuse_file_info* fi)
{
Filesystem* fs = FUSE_FS;
Inode* inode = fs->GetInode((iso9660_ino_t) fi->fh);
if ( !inode )
return -errno;
if ( !S_ISDIR(inode->Mode()) )
return inode->Unref(), -(errno = ENOTDIR);
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( inode->ReadDirectory(&offset, &block, &block_id,
rec_num ? NULL : name, &file_type, &inode_id) )
{
if ( !rec_num || !rec_num-- )
{
if ( filler(buf, name, NULL, 0) )
{
block->Unref();
inode->Unref();
return 0;
}
}
}
int errnum = errno;
if ( block )
block->Unref();
inode->Unref();
if ( errnum )
return -errnum;
return 0;
}
/*int iso9660_fuse_lock(const char*, struct fuse_file_info*, int, struct flock*)
{
return -(errno = ENOSYS);
}*/
int iso9660_fuse_utimens(const char* path, const struct timespec tv[2])
{
Inode* inode = iso9660_fuse_resolve_path(path);
if ( !inode )
return -errno;
(void) tv;
return inode->Unref(), -(errno = EROFS);
}
/*int iso9660_fuse_bmap(const char*, size_t blocksize, uint64_t* idx)
{
return -(errno = ENOSYS);
}*/
int iso9660_fuse_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev)
{
struct fuse_operations operations;
memset(&operations, 0, sizeof(operations));
operations.access = iso9660_fuse_access;
operations.chmod = iso9660_fuse_chmod;
operations.chown = iso9660_fuse_chown;
operations.create = iso9660_fuse_create;
operations.destroy = iso9660_fuse_destroy;
operations.fgetattr = iso9660_fuse_fgetattr;
operations.flush = iso9660_fuse_flush;
operations.fsync = iso9660_fuse_fsync;
operations.ftruncate = iso9660_fuse_ftruncate;
operations.getattr = iso9660_fuse_getattr;
operations.init = iso9660_fuse_init;
operations.link = iso9660_fuse_link;
operations.mkdir = iso9660_fuse_mkdir;
operations.mknod = iso9660_fuse_mknod;
operations.opendir = iso9660_fuse_opendir;
operations.open = iso9660_fuse_open;
operations.readdir = iso9660_fuse_readdir;
operations.read = iso9660_fuse_read;
operations.readlink = iso9660_fuse_readlink;
operations.releasedir = iso9660_fuse_releasedir;
operations.release = iso9660_fuse_release;
operations.rename = iso9660_fuse_rename;
operations.rmdir = iso9660_fuse_rmdir;
operations.statfs = iso9660_fuse_statfs;
operations.symlink = iso9660_fuse_symlink;
operations.truncate = iso9660_fuse_truncate;
operations.unlink = iso9660_fuse_unlink;
operations.utimens = iso9660_fuse_utimens;
operations.write = iso9660_fuse_write;
operations.flag_nullpath_ok = 1;
operations.flag_nopath = 1;
char* argv_fuse[] =
{
(char*) argv0,
(char*) "-s",
(char*) (foreground ? "-f" : mount_path),
(char*) (foreground ? mount_path : NULL),
(char*) NULL,
};
int argc_fuse = sizeof(argv_fuse) / sizeof(argv_fuse[0]) - 1;
struct iso9660_fuse_ctx iso9660_fuse_ctx;
iso9660_fuse_ctx.fs = fs;
iso9660_fuse_ctx.dev = dev;
return fuse_main(argc_fuse, argv_fuse, &operations, &iso9660_fuse_ctx);
}
#endif

32
iso9660/fuse.h Normal file
View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* fuse.h
* FUSE frontend.
*/
#ifndef FUSE_H
#define FUSE_H
class Device;
class Filesystem;
int iso9660_fuse_main(const char* argv0,
const char* mount_path,
bool foreground,
Filesystem* fs,
Device* dev);
#endif

614
iso9660/inode.cpp Normal file
View file

@ -0,0 +1,614 @@
/*
* Copyright (c) 2013, 2014, 2015, 2018, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* inode.cpp
* Filesystem inode.
*/
#define __STDC_CONSTANT_MACROS
#define __STDC_LIMIT_MACROS
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <endian.h>
#include <fcntl.h>
#include <limits.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "inode.h"
#include "iso9660fs.h"
#include "util.h"
#ifndef S_SETABLE
#define S_SETABLE 02777
#endif
#ifndef O_WRITE
#define O_WRITE (O_WRONLY | O_RDWR)
#endif
Inode::Inode(Filesystem* filesystem, iso9660_ino_t inode_id)
{
this->prev_inode = NULL;
this->next_inode = NULL;
this->prev_hashed = NULL;
this->next_hashed = NULL;
this->data_block = NULL;
this->data = NULL;
this->filesystem = filesystem;
this->reference_count = 1;
this->remote_reference_count = 0;
this->inode_id = inode_id;
this->parent_inode_id = 0;
}
Inode::~Inode()
{
if ( data_block )
data_block->Unref();
Unlink();
}
void Inode::Parse()
{
const uint8_t* block_data = (const uint8_t*) data;
uid = 0;
gid = 0;
time = 0;
uint8_t file_flags = block_data[25];
bool is_directory = file_flags & ISO9660_DIRENT_FLAG_DIR;
mode = 0555 | (is_directory ? ISO9660_S_IFDIR : ISO9660_S_IFREG);
uint32_t u32;
memcpy(&u32, block_data + 10, sizeof(u32));
size = le32toh(u32);
nlink = 1;
const uint8_t* time_bytes = block_data + 18;
struct tm tm;
memset(&tm, 0, sizeof(tm));
tm.tm_year = time_bytes[0];
tm.tm_mon = time_bytes[1] - 1;
tm.tm_mday = time_bytes[2];
tm.tm_hour = time_bytes[3];
tm.tm_min = time_bytes[4];
tm.tm_sec = time_bytes[5];
// TODO: Is this accurate?
time_t tz_offset = (-48 + time_bytes[6]) * 15 * 60;
// TODO: The timezone offset should've been mixed in with mktime, somehow.
time = mktime(&tm) + tz_offset;
uint8_t dirent_len = block_data[0];
uint8_t name_len = block_data[32];
size_t extended_off = 33 + name_len + !(name_len & 1);
for ( size_t i = extended_off; i < dirent_len && 3 <= dirent_len - i; )
{
const uint8_t* field = block_data + i;
uint8_t len = field[2];
if ( !len || dirent_len - i < len )
break;
i += len;
if ( len == 36 && field[0] == 'P' && field[1] == 'X' && field[3] == 1 )
{
uint32_t bits;
memcpy(&bits, field + 4, sizeof(bits));
mode = le32toh(bits) & 0xffff;
memcpy(&bits, field + 12, sizeof(bits));
nlink = le32toh(bits);
memcpy(&bits, field + 20, sizeof(bits));
uid = le32toh(bits);
memcpy(&bits, field + 28, sizeof(bits));
gid = le32toh(bits);
}
}
if ( ISO9660_S_ISLNK(mode) )
{
uint8_t buf[256];
size = ReadLink(buf, sizeof(buf));
}
}
uint32_t Inode::Mode()
{
return mode;
}
uint32_t Inode::UserId()
{
return uid;
}
uint32_t Inode::GroupId()
{
return gid;
}
uint64_t Inode::Size()
{
return size;
}
uint64_t Inode::Time()
{
return time;
}
Block* Inode::GetBlock(uint32_t offset)
{
uint32_t lba;
memcpy(&lba, (uint8_t*) data + 2, sizeof(lba));
lba = le32toh(lba);
uint32_t block_id = lba + offset;
return filesystem->device->GetBlock(block_id);
}
bool Inode::FindParentInode(uint64_t parent_lba, uint64_t parent_size)
{
if ( inode_id == filesystem->root_ino )
return parent_inode_id = inode_id, true;
Block* block = NULL;
uint32_t block_size = filesystem->block_size;
uint64_t parent_offset = parent_lba * block_size;
uint64_t block_id = 0;
uint64_t offset = 0;
while ( offset < parent_size )
{
uint64_t entry_block_id = (parent_offset + offset) / block_size;
uint64_t entry_block_offset = (parent_offset + offset) % block_size;
if ( block && block_id != entry_block_id )
{
block->Unref();
block = NULL;
}
if ( !block && !(block = filesystem->device->GetBlock(entry_block_id)) )
return false;
const uint8_t* block_data = block->block_data + entry_block_offset;
uint8_t dirent_len = block_data[0];
if ( !dirent_len )
{
offset = (entry_block_id + 1) * block_size;
continue;
}
uint8_t name_len = block_data[32];
const uint8_t* name_data = block_data + 33;
if ( name_len == 0 || !name_data[0] )
{
parent_inode_id = parent_offset + offset;
return true;
}
// TODO: Can dirent_len be misaligned?
uint64_t reclen = dirent_len + (dirent_len & 1);
if ( !reclen )
return errno = EINVAL, false;
offset += reclen;
}
return errno = EINVAL, false;
}
bool Inode::ReadDirectory(uint64_t* offset_inout,
Block** block_inout,
uint64_t* block_id_inout,
char* name,
uint8_t* file_type_out,
iso9660_ino_t* inode_id_out)
{
uint64_t offset = *offset_inout;
next_block:
uint64_t filesize = Size();
if ( filesize <= offset )
return errno = 0, false;
uint64_t entry_block_id = offset / filesystem->block_size;
uint64_t entry_block_offset = offset % filesystem->block_size;
if ( *block_inout && *block_id_inout != entry_block_id )
{
(*block_inout)->Unref();
(*block_inout) = NULL;
}
if ( !*block_inout &&
!(*block_inout = GetBlock(*block_id_inout = entry_block_id)) )
return false;
const uint8_t* block_data = (*block_inout)->block_data + entry_block_offset;
uint8_t dirent_len = block_data[0];
if ( !dirent_len )
{
offset = (entry_block_id + 1) * filesystem->block_size;
goto next_block;
}
if ( name )
{
uint8_t name_len = block_data[32];
const uint8_t* name_data = block_data + 33;
size_t extended_off = 33 + name_len + !(name_len & 1);
iso9660_ino_t entry_inode_id =
((*block_inout)->block_id * filesystem->block_size) +
entry_block_offset;
// TODO: The root directory inode should be that of its . entry.
if ( name_len == 0 || !name_data[0] )
{
name[0] = '.';
name[1] = '\0';
name_len = 1;
entry_inode_id = inode_id;
}
else if ( name_len == 1 && name_data[0] == 1 )
{
name[0] = '.';
name[1] = '.';
name[2] = '\0';
name_len = 2;
if ( !parent_inode_id )
{
uint32_t parent_lba;
memcpy(&parent_lba, block_data + 2, sizeof(parent_lba));
parent_lba = le32toh(parent_lba);
uint32_t parent_size;
memcpy(&parent_size, block_data + 10, sizeof(parent_size));
parent_size = le32toh(parent_size);
if ( !FindParentInode(parent_lba, parent_size) )
return false;
}
entry_inode_id = parent_inode_id;
}
else
{
for ( size_t i = 0; i < name_len; i++ )
{
if ( name_data[i] == ';' )
{
name_len = i;
break;
}
name[i] = tolower(name_data[i]);
}
name[name_len] = '\0';
}
for ( size_t i = extended_off; i < dirent_len && 3 <= dirent_len - i; )
{
const uint8_t* field = block_data + i;
uint8_t len = field[2];
if ( !len || dirent_len - i < len )
break;
i += len;
if ( 5 <= len && field[0] == 'N' && field[1] == 'M' &&
field[3] == 1 )
{
uint8_t nm_flags = field[4];
if ( nm_flags & (1 << 0) ) // TODO: Continued names.
break;
name_len = len - 5;
memcpy(name, field + 5, name_len);
name[name_len] = '\0';
}
// TODO: Other extensions.
}
uint8_t file_flags = block_data[25];
// TODO: Rock Ridge.
bool is_directory = file_flags & ISO9660_DIRENT_FLAG_DIR;
uint8_t file_type = is_directory ? ISO9660_FT_DIR : ISO9660_FT_REG_FILE;
*file_type_out = file_type;
*inode_id_out = entry_inode_id;
}
// TODO: Can dirent_len be misaligned?
uint64_t reclen = dirent_len + (dirent_len & 1);
if ( !reclen )
return errno = EINVAL, false;
offset += reclen;
*offset_inout = offset;
return true;
}
Inode* Inode::Open(const char* elem, int flags, mode_t mode)
{
if ( !ISO9660_S_ISDIR(Mode()) )
return errno = ENOTDIR, (Inode*) NULL;
size_t elem_length = strlen(elem);
if ( elem_length == 0 )
return errno = ENOENT, (Inode*) NULL;
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( ReadDirectory(&offset, &block, &block_id, name, &file_type,
&inode_id) )
{
size_t name_len = strlen(name);
if ( name_len == elem_length && memcmp(elem, name, elem_length) == 0 )
{
block->Unref();
if ( (flags & O_CREAT) && (flags & O_EXCL) )
return errno = EEXIST, (Inode*) NULL;
if ( (flags & O_DIRECTORY) &&
file_type != ISO9660_FT_UNKNOWN &&
file_type != ISO9660_FT_DIR &&
file_type != ISO9660_FT_SYMLINK )
return errno = ENOTDIR, (Inode*) NULL;
Inode* inode = filesystem->GetInode(inode_id);
if ( !inode )
return (Inode*) NULL;
if ( flags & O_DIRECTORY &&
!ISO9660_S_ISDIR(inode->Mode()) &&
!ISO9660_S_ISLNK(inode->Mode()) )
{
inode->Unref();
return errno = ENOTDIR, (Inode*) NULL;
}
if ( flags & O_WRITE )
{
inode->Unref();
return errno = EROFS, (Inode*) NULL;
}
return inode;
}
}
if ( block )
block->Unref();
if ( flags & O_CREAT )
{
(void) mode;
return errno = EROFS, (Inode*) NULL;
}
return errno = ENOENT, (Inode*) NULL;
}
bool Inode::Link(const char* elem, Inode* dest)
{
if ( !ISO9660_S_ISDIR(Mode()) )
return errno = ENOTDIR, false;
if ( ISO9660_S_ISDIR(dest->Mode()) )
return errno = EISDIR, false;
size_t elem_length = strlen(elem);
if ( elem_length == 0 )
return errno = ENOENT, false;
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( ReadDirectory(&offset, &block, &block_id, name, &file_type,
&inode_id) )
{
size_t name_len = strlen(name);
if ( name_len == elem_length && memcmp(elem, name, elem_length) == 0 )
{
block->Unref();
return errno = EEXIST, false;
}
}
if ( block )
block->Unref();
return errno = EROFS, false;
}
Inode* Inode::UnlinkKeep(const char* elem, bool directories, bool force)
{
if ( !ISO9660_S_ISDIR(Mode()) )
return errno = ENOTDIR, (Inode*) NULL;
size_t elem_length = strlen(elem);
if ( elem_length == 0 )
return errno = ENOENT, (Inode*) NULL;
uint64_t offset = 0;
Block* block = NULL;
uint64_t block_id = 0;
char name[256];
uint8_t file_type;
iso9660_ino_t inode_id;
while ( ReadDirectory(&offset, &block, &block_id, name, &file_type,
&inode_id) )
{
size_t name_len = strlen(name);
if ( name_len == elem_length && memcmp(elem, name, elem_length) == 0 )
{
(void) directories;
(void) force;
block->Unref();
return errno = EROFS, (Inode*) NULL;
}
}
if ( block )
block->Unref();
return errno = ENOENT, (Inode*) NULL;
}
bool Inode::Unlink(const char* elem, bool directories, bool force)
{
Inode* result = UnlinkKeep(elem, directories, force);
if ( !result )
return false;
result->Unref();
return true;
}
ssize_t Inode::ReadLink(uint8_t* buf, size_t bufsize)
{
size_t result = 0;
const uint8_t* block_data = (const uint8_t*) data;
uint8_t dirent_len = block_data[0];
uint8_t name_len = block_data[32];
size_t extended_off = 33 + name_len + !(name_len & 1);
bool continued = true;
for ( size_t i = extended_off; i < dirent_len && 3 <= dirent_len - i; )
{
const uint8_t* field = block_data + i;
uint8_t len = field[2];
if ( !len || dirent_len - i < len )
break;
i += len;
if ( 5 <= len && field[0] == 'S' && field[1] == 'L' && field[3] == 1 )
{
for ( size_t n = 5; n < len && 2 <= len - n; )
{
uint8_t comp_flags = field[n + 0];
uint8_t comp_len = field[n + 1];
if ( len - (n + 2) < comp_len )
break;
const char* data = (const char*) (field + n + 2);
size_t datalen = comp_len;
// TODO: How is a trailing slash encoded?
if ( !continued || (comp_flags & (1 << 3) /* root */) )
{
buf[result++] = '/';
if ( result == bufsize )
return (ssize_t) result;
}
if ( comp_flags & (1 << 1) )
{
data = ".";
datalen = 1;
}
else if ( comp_flags & (1 << 2) )
{
data = "..";
datalen = 2;
}
size_t possible = bufsize - result;
size_t count = datalen < possible ? datalen : possible;
memcpy(buf + result, data, count);
result += count;
if ( result == bufsize )
return (ssize_t) result;
continued = comp_flags & (1 << 0);
n += 2 + comp_len;
}
}
}
return (ssize_t) result;
}
ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset)
{
if ( !ISO9660_S_ISREG(Mode()) && !ISO9660_S_ISLNK(Mode()) )
return errno = EISDIR, -1;
if ( o_offset < 0 )
return errno = EINVAL, -1;
if ( SSIZE_MAX < s_count )
s_count = SSIZE_MAX;
uint64_t sofar = 0;
uint64_t count = (uint64_t) s_count;
uint64_t offset = (uint64_t) o_offset;
uint64_t file_size = Size();
if ( file_size <= offset )
return 0;
if ( file_size - offset < count )
count = file_size - offset;
while ( sofar < count )
{
uint64_t block_id = offset / filesystem->block_size;
uint32_t block_offset = offset % filesystem->block_size;
uint32_t block_left = filesystem->block_size - block_offset;
Block* block = GetBlock(block_id);
if ( !block )
return sofar ? sofar : -1;
size_t amount = count - sofar < block_left ? count - sofar : block_left;
memcpy(buf + sofar, block->block_data + block_offset, amount);
sofar += amount;
offset += amount;
block->Unref();
}
return (ssize_t) sofar;
}
bool Inode::Rename(Inode* olddir, const char* oldname, const char* newname)
{
if ( !strcmp(oldname, ".") || !strcmp(oldname, "..") ||
!strcmp(newname, ".") || !strcmp(newname, "..") )
return errno = EPERM, false;
Inode* src_inode = olddir->Open(oldname, O_RDONLY, 0);
if ( !src_inode )
return false;
if ( Inode* dst_inode = Open(newname, O_RDONLY, 0) )
{
bool same_inode = src_inode->inode_id == dst_inode->inode_id;
dst_inode->Unref();
if ( same_inode )
return src_inode->Unref(), true;
}
src_inode->Unref();
return errno = EROFS, false;
}
bool Inode::RemoveDirectory(const char* path)
{
return UnlinkKeep(path, true);
}
void Inode::Refer()
{
reference_count++;
}
void Inode::Unref()
{
assert(0 < reference_count);
reference_count--;
if ( !reference_count && !remote_reference_count )
delete this;
}
void Inode::RemoteRefer()
{
remote_reference_count++;
}
void Inode::RemoteUnref()
{
assert(0 < remote_reference_count);
remote_reference_count--;
if ( !reference_count && !remote_reference_count )
delete this;
}
void Inode::Use()
{
data_block->Use();
Unlink();
Prelink();
}
void Inode::Unlink()
{
(prev_inode ? prev_inode->next_inode : filesystem->mru_inode) = next_inode;
(next_inode ? next_inode->prev_inode : filesystem->lru_inode) = prev_inode;
size_t bin = inode_id % INODE_HASH_LENGTH;
(prev_hashed ? prev_hashed->next_hashed : filesystem->hash_inodes[bin]) = next_hashed;
if ( next_hashed ) next_hashed->prev_hashed = prev_hashed;
}
void Inode::Prelink()
{
prev_inode = NULL;
next_inode = filesystem->mru_inode;
if ( filesystem->mru_inode )
filesystem->mru_inode->prev_inode = this;
filesystem->mru_inode = this;
if ( !filesystem->lru_inode )
filesystem->lru_inode = this;
size_t bin = inode_id % INODE_HASH_LENGTH;
prev_hashed = NULL;
next_hashed = filesystem->hash_inodes[bin];
filesystem->hash_inodes[bin] = this;
if ( next_hashed )
next_hashed->prev_hashed = this;
}

83
iso9660/inode.h Normal file
View file

@ -0,0 +1,83 @@
/*
* Copyright (c) 2013, 2014, 2015, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* inode.h
* Filesystem inode.
*/
#ifndef INODE_H
#define INODE_H
#include "iso9660.h"
class Block;
class Filesystem;
class Inode
{
public:
Inode(Filesystem* filesystem, iso9660_ino_t inode_id);
~Inode();
public:
Inode* prev_inode;
Inode* next_inode;
Inode* prev_hashed;
Inode* next_hashed;
Block* data_block;
struct iso9660_dirent* data;
Filesystem* filesystem;
size_t reference_count;
size_t remote_reference_count;
iso9660_ino_t inode_id;
iso9660_ino_t parent_inode_id;
uint32_t uid;
uint32_t gid;
uint64_t size;
uint64_t time;
uint32_t mode;
uint32_t nlink;
public:
void Parse();
uint32_t Mode();
uint32_t UserId();
uint32_t GroupId();
uint64_t Size();
uint64_t Time();
Block* GetBlock(uint32_t offset);
bool FindParentInode(uint64_t parent_lba, uint64_t parent_size);
bool ReadDirectory(uint64_t* offset_inout, Block** block_inout,
uint64_t* block_id_inout, char* name,
uint8_t* file_type_out, iso9660_ino_t* inode_id_out);
Inode* Open(const char* elem, int flags, mode_t mode);
bool Link(const char* elem, Inode* dest);
bool Unlink(const char* elem, bool directories, bool force=false);
Inode* UnlinkKeep(const char* elem, bool directories, bool force=false);
ssize_t ReadLink(uint8_t* buf, size_t bufsize);
ssize_t ReadAt(uint8_t* buffer, size_t count, off_t offset);
bool Rename(Inode* olddir, const char* oldname, const char* newname);
bool RemoveDirectory(const char* path);
void Refer();
void Unref();
void RemoteRefer();
void RemoteUnref();
void Use();
void Unlink();
void Prelink();
};
#endif

150
iso9660/ioleast.h Normal file
View file

@ -0,0 +1,150 @@
/*
* Copyright (c) 2012, 2013, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* ioleast.h
* Versions of {,p}{read,write} that don't return until it has returned as much
* data as requested, end of file, or an error occurs. This is sometimes needed
* as read(2) and write(2) is not always guaranteed to fill up the entire
* buffer or write it all.
*/
#ifndef SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H
#define SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H
#if defined(__sortix__) || defined(__sortix_libc__)
#include_next <ioleast.h>
#else
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>
#if !defined(EEOF) && defined(EIO)
#define EEOF EIO
#endif
__attribute__((unused)) static inline
size_t readleast(int fd, void* buf_ptr, size_t least, size_t max)
{
assert(least <= max);
unsigned char* buf = (unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = read(fd, buf + done, max - done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t writeleast(int fd, const void* buf_ptr, size_t least, size_t max)
{
assert(least <= max);
const unsigned char* buf = (const unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = write(fd, buf + done, max - done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t preadleast(int fd, void* buf_ptr, size_t least, size_t max, off_t off)
{
assert(least <= max);
unsigned char* buf = (unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = pread(fd, buf + done, max - done, off + done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t pwriteleast(int fd, const void* buf_ptr, size_t least, size_t max, off_t off)
{
assert(least <= max);
const unsigned char* buf = (const unsigned char*) buf_ptr;
size_t done = 0;
do
{
ssize_t amount = pwrite(fd, buf + done, max - done, off + done);
if ( amount < 0 )
return done;
if ( !amount && done < least )
return errno = EEOF, done;
if ( !amount )
break;
done += amount;
} while ( done < least );
return done;
}
__attribute__((unused)) static inline
size_t readall(int fd, void* buf, size_t count)
{
return readleast(fd, buf, count, count);
}
__attribute__((unused)) static inline
size_t writeall(int fd, const void* buf, size_t count)
{
return writeleast(fd, buf, count, count);
}
__attribute__((unused)) static inline
size_t preadall(int fd, void* buf, size_t count, off_t off)
{
return preadleast(fd, buf, count, count, off);
}
__attribute__((unused)) static inline
size_t pwriteall(int fd, const void* buf, size_t count, off_t off)
{
return pwriteleast(fd, buf, count, count, off);
}
#endif
#endif

128
iso9660/iso9660.h Normal file
View file

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* iso9660.h
* Data structures for the ISO 9660 filesystem.
*/
#ifndef ISO9660_H
#define ISO9660_H
#include <stdint.h>
const uint8_t TYPE_BOOT_RECORD = 0x00;
const uint8_t TYPE_PRIMARY_VOLUME_DESCRIPTOR = 0x01;
const uint8_t TYPE_VOLUME_DESCRIPTOR_SET_TERMINATOR = 0xFF;
struct iso9660_pvd /* primary volume descriptor */
{
uint8_t type;
char standard_identifier[5];
uint8_t version;
uint8_t unused1;
char system_identifier[32];
char volume_identifier[32];
uint8_t unused2[8];
uint32_t volume_space_size_le;
uint32_t volume_space_size_be;
uint8_t unused3[32];
uint16_t volume_set_size_le;
uint16_t volume_set_size_be;
uint16_t volume_sequence_number_le;
uint16_t volume_sequence_number_be;
uint16_t logical_block_size_le;
uint16_t logical_block_size_be;
uint32_t path_table_size_le;
uint32_t path_table_size_be;
uint32_t path_table_lba_le;
uint32_t path_table_opt_lba_le;
uint32_t path_table_lba_be;
uint32_t path_table_opt_lba_be;
uint8_t root_dirent[34];
char volume_set_identifier[128];
char publisher_identifier[128];
char data_preparer_identifier[128];
char application_identifier[128];
char copyright_file_identifier[37];
char abstract_file_identifier[37];
char bibliographic_file_identifier[37];
char creation_datetime[17];
char modification_datetime[17];
char expiration_datetime[17];
char effective_datetime[17];
uint8_t file_structure_version;
uint8_t unused4;
uint8_t application_use[512];
uint8_t reserved[653];
};
static_assert(sizeof(struct iso9660_pvd) == 2048,
"sizeof(struct iso9660_pvd) == 2048");
typedef uint64_t iso9660_ino_t;
struct iso9660_dirent
{
uint8_t unused;
};
#define ISO9660_DIRENT_FLAG_DIR (1 << 1)
#define ISO9660_S_IFMT (0170000)
#define ISO9660_S_IFIFO (0010000)
#define ISO9660_S_IFCHR (0020000)
#define ISO9660_S_IFDIR (0040000)
#define ISO9660_S_IFBLK (0060000)
#define ISO9660_S_IFREG (0100000)
#define ISO9660_S_IFLNK (0120000)
#define ISO9660_S_IFSOCK (0140000)
#define ISO9660_S_ISSOCK(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFSOCK)
#define ISO9660_S_ISLNK(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFLNK)
#define ISO9660_S_ISREG(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFREG)
#define ISO9660_S_ISBLK(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFBLK)
#define ISO9660_S_ISDIR(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFDIR)
#define ISO9660_S_ISCHR(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFCHR)
#define ISO9660_S_ISFIFO(mode) (((mode) & ISO9660_S_IFMT) == ISO9660_S_IFIFO)
static const uint8_t ISO9660_FT_UNKNOWN = 0;
static const uint8_t ISO9660_FT_REG_FILE = 1;
static const uint8_t ISO9660_FT_DIR = 2;
static const uint8_t ISO9660_FT_CHRDEV = 3;
static const uint8_t ISO9660_FT_BLKDEV = 4;
static const uint8_t ISO9660_FT_FIFO = 5;
static const uint8_t ISO9660_FT_SOCK = 6;
static const uint8_t ISO9660_FT_SYMLINK = 7;
static inline uint8_t ISO9660_FT_OF_MODE(mode_t mode)
{
if ( ISO9660_S_ISREG(mode) )
return ISO9660_FT_REG_FILE;
if ( ISO9660_S_ISDIR(mode) )
return ISO9660_FT_DIR;
if ( ISO9660_S_ISCHR(mode) )
return ISO9660_FT_CHRDEV;
if ( ISO9660_S_ISBLK(mode) )
return ISO9660_FT_BLKDEV;
if ( ISO9660_S_ISFIFO(mode) )
return ISO9660_FT_FIFO;
if ( ISO9660_S_ISSOCK(mode) )
return ISO9660_FT_SOCK;
if ( ISO9660_S_ISLNK(mode) )
return ISO9660_FT_SYMLINK;
return ISO9660_FT_UNKNOWN;
}
#endif

278
iso9660/iso9660fs.cpp Normal file
View file

@ -0,0 +1,278 @@
/*
* Copyright (c) 2013, 2014, 2015, 2016, 2022, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* iso9660fs.cpp
* Implementation of the ISO 9660 filesystem.
*/
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <dirent.h>
#include <endian.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if defined(__sortix__)
#include "fsmarshall.h"
#else
#include "fuse.h"
#endif
#include "block.h"
#include "device.h"
#include "filesystem.h"
#include "inode.h"
#include "ioleast.h"
#include "iso9660.h"
#include "iso9660fs.h"
#include "util.h"
uid_t request_uid;
uid_t request_gid;
mode_t HostModeFromFsMode(uint32_t mode)
{
mode_t hostmode = mode & 07777;
if ( ISO9660_S_ISSOCK(mode) ) hostmode |= S_IFSOCK;
if ( ISO9660_S_ISLNK(mode) ) hostmode |= S_IFLNK;
if ( ISO9660_S_ISREG(mode) ) hostmode |= S_IFREG;
if ( ISO9660_S_ISBLK(mode) ) hostmode |= S_IFBLK;
if ( ISO9660_S_ISDIR(mode) ) hostmode |= S_IFDIR;
if ( ISO9660_S_ISCHR(mode) ) hostmode |= S_IFCHR;
if ( ISO9660_S_ISFIFO(mode) ) hostmode |= S_IFIFO;
return hostmode;
}
uint32_t FsModeFromHostMode(mode_t hostmode)
{
uint32_t mode = hostmode & 07777;
if ( S_ISSOCK(hostmode) ) mode |= ISO9660_S_IFSOCK;
if ( S_ISLNK(hostmode) ) mode |= ISO9660_S_IFLNK;
if ( S_ISREG(hostmode) ) mode |= ISO9660_S_IFREG;
if ( S_ISBLK(hostmode) ) mode |= ISO9660_S_IFBLK;
if ( S_ISDIR(hostmode) ) mode |= ISO9660_S_IFDIR;
if ( S_ISCHR(hostmode) ) mode |= ISO9660_S_IFCHR;
if ( S_ISFIFO(hostmode) ) mode |= ISO9660_S_IFIFO;
return mode;
}
uint8_t HostDTFromFsDT(uint8_t fsdt)
{
switch ( fsdt )
{
case ISO9660_FT_UNKNOWN: return DT_UNKNOWN;
case ISO9660_FT_REG_FILE: return DT_REG;
case ISO9660_FT_DIR: return DT_DIR;
case ISO9660_FT_CHRDEV: return DT_CHR;
case ISO9660_FT_BLKDEV: return DT_BLK;
case ISO9660_FT_FIFO: return DT_FIFO;
case ISO9660_FT_SOCK: return DT_SOCK;
case ISO9660_FT_SYMLINK: return DT_LNK;
}
return DT_UNKNOWN;
}
void StatInode(Inode* inode, struct stat* st)
{
memset(st, 0, sizeof(*st));
st->st_ino = inode->inode_id;
st->st_mode = HostModeFromFsMode(inode->Mode());
st->st_nlink = inode->nlink;
st->st_uid = inode->uid;
st->st_gid = inode->gid;
st->st_size = inode->size;
// TODO: Rock Ridge.
st->st_atim.tv_sec = inode->Time();
st->st_atim.tv_nsec = 0;
// TODO: Rock Ridge.
st->st_ctim.tv_sec = inode->Time();
st->st_ctim.tv_nsec = 0;
// TODO: Rock Ridge.
st->st_mtim.tv_sec = inode->Time();
st->st_mtim.tv_nsec = 0;
st->st_blksize = inode->filesystem->block_size;
st->st_blocks = divup(st->st_size, (off_t) 512);
}
static void compact_arguments(int* argc, char*** argv)
{
for ( int i = 0; i < *argc; i++ )
{
while ( i < *argc && !(*argv)[i] )
{
for ( int n = i; n < *argc; n++ )
(*argv)[n] = (*argv)[n+1];
(*argc)--;
}
}
}
static void help(FILE* fp, const char* argv0)
{
fprintf(fp, "Usage: %s [OPTION]... DEVICE [MOUNT-POINT]\n", argv0);
}
static void version(FILE* fp, const char* argv0)
{
fprintf(fp, "%s (Sortix) %s\n", argv0, VERSIONSTR);
}
int main(int argc, char* argv[])
{
const char* argv0 = argv[0];
const char* pretend_mount_path = NULL;
bool foreground = false;
for ( int i = 1; i < argc; i++ )
{
const char* arg = argv[i];
if ( arg[0] != '-' || !arg[1] )
continue;
argv[i] = NULL;
if ( !strcmp(arg, "--") )
break;
if ( arg[1] != '-' )
{
while ( char c = *++arg ) switch ( c )
{
case 'r': break;
default:
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
help(stderr, argv0);
exit(1);
}
}
else if ( !strcmp(arg, "--help") )
help(stdout, argv0), exit(0);
else if ( !strcmp(arg, "--version") )
version(stdout, argv0), exit(0);
else if ( !strcmp(arg, "--background") )
foreground = false;
else if ( !strcmp(arg, "--foreground") )
foreground = true;
else if ( !strcmp(arg, "--read") )
{
}
else if ( !strncmp(arg, "--pretend-mount-path=", strlen("--pretend-mount-path=")) )
pretend_mount_path = arg + strlen("--pretend-mount-path=");
else if ( !strcmp(arg, "--pretend-mount-path") )
{
if ( i+1 == argc )
{
fprintf(stderr, "%s: --pretend-mount-path: Missing operand\n", argv0);
exit(1);
}
pretend_mount_path = argv[++i];
argv[i] = NULL;
}
else
{
fprintf(stderr, "%s: unknown option: %s\n", argv0, arg);
help(stderr, argv0);
exit(1);
}
}
if ( argc == 1 )
{
help(stdout, argv0);
exit(0);
}
compact_arguments(&argc, &argv);
const char* device_path = 2 <= argc ? argv[1] : NULL;
const char* mount_path = 3 <= argc ? argv[2] : NULL;
if ( !device_path )
{
help(stderr, argv0);
exit(1);
}
if ( !pretend_mount_path )
pretend_mount_path = mount_path;
int fd = open(device_path, O_RDONLY);
if ( fd < 0 )
err(1, "%s", device_path);
// Search for the Primary Volume Descriptor.
off_t block_size = 2048;
struct iso9660_pvd pvd;
off_t pvd_offset;
for ( uint32_t i = 16; true; i++ )
{
pvd_offset = block_size * i;
if ( preadall(fd, &pvd, sizeof(pvd), pvd_offset) != sizeof(pvd) )
{
if ( errno == EEOF )
errx(1, "Not a valid ISO 9660 filesystem: %s", device_path);
else
err(1, "read: %s", device_path);
}
if ( memcmp(pvd.standard_identifier, "CD001", 5) != 0 )
errx(1, "Not a valid ISO 9660 filesystem: %s", device_path);
if ( pvd.type == TYPE_PRIMARY_VOLUME_DESCRIPTOR )
break;
if ( pvd.type == TYPE_VOLUME_DESCRIPTOR_SET_TERMINATOR )
errx(1, "Not a valid ISO 9660 filesystem: %s", device_path);
}
if ( pvd.version != 1 || pvd.file_structure_version != 1 )
errx(1, "Unsupported ISO 9660 filesystem version: %s", device_path);
// TODO: Test if appropriate power of two and allow.
if ( le32toh(pvd.logical_block_size_le) != block_size )
errx(1, "Unsupported ISO 9660 block size: %s", device_path);
block_size = le32toh(pvd.logical_block_size_le);
Device* dev = new Device(fd, device_path, block_size);
if ( !dev )
err(1, "malloc");
Filesystem* fs = new Filesystem(dev, pretend_mount_path, pvd_offset);
if ( !fs )
err(1, "%s", device_path);
// Change the root inode to be its own . entry instead of the pvd record, so
// parent directories correctly .. to it, and so Rock Ridge extensions work.
Inode* root = fs->GetInode(fs->root_ino);
if ( !root )
err(1, "%s: GetInode", device_path);
fs->root_ino = 0;
uint32_t root_lba;
uint32_t root_size;
memcpy(&root_lba, pvd.root_dirent + 2, sizeof(root_lba));
memcpy(&root_size, pvd.root_dirent + 10, sizeof(root_size));
root->FindParentInode(root_lba, root_size);
fs->root_ino = root->parent_inode_id;
root->Unref();
if ( !mount_path )
return 0;
#if defined(__sortix__)
return fsmarshall_main(argv0, mount_path, foreground, fs, dev);
#else
return iso9660_fuse_main(argv0, mount_path, foreground, fs, dev);
#endif
}

33
iso9660/iso9660fs.h Normal file
View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* iso9660fs.h
* Implementation of the ISO 9660 filesystem.
*/
#ifndef ISO9660FS_H
#define ISO9660FS_H
extern uid_t request_uid;
extern gid_t request_gid;
class Inode;
mode_t HostModeFromFsMode(uint32_t fsmode);
uint32_t FsModeFromHostMode(mode_t hostmode);
uint8_t HostDTFromFsDT(uint8_t fsdt);
void StatInode(Inode* inode, struct stat* st);
#endif

49
iso9660/util.h Normal file
View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2013, 2015 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* util.h
* Utility functions for the filesystem implementation.
*/
#ifndef UTIL_H
#define UTIL_H
template <class T> T divup(T a, T b)
{
return a/b + (a % b ? 1 : 0);
}
template <class T> T roundup(T a, T b)
{
return a % b ? a + b - a % b : a;
}
static inline bool checkbit(const uint8_t* bitmap, size_t bit)
{
uint8_t bits = bitmap[bit / 8UL];
return bits & (1U << (bit % 8UL));
}
static inline void setbit(uint8_t* bitmap, size_t bit)
{
bitmap[bit / 8UL] |= 1U << (bit % 8UL);
}
static inline void clearbit(uint8_t* bitmap, size_t bit)
{
bitmap[bit / 8UL] &= ~(1U << (bit % 8UL));
}
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2014, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -29,6 +29,7 @@
namespace Sortix {
size_t aux_allocated = 0;
size_t aux_leaked = 0;
size_t heap_allocated = 0;
kthread_mutex_t alloc_lock = KTHREAD_MUTEX_INITIALIZER;
@ -69,6 +70,8 @@ void FreeKernelAddress(addralloc_t* alloc)
addr_t aux_reached = kmem_from + kmem_size - aux_allocated;
if ( alloc->from == aux_reached )
aux_allocated -= alloc->size;
else
aux_leaked += alloc->size;
alloc->from = 0;
alloc->size = 0;
@ -98,6 +101,17 @@ void ShrinkHeap(size_t decrease)
heap_allocated -= decrease;
}
void KernelAddressStatistics(size_t* heap, size_t* aux, size_t* leaked,
size_t* total)
{
ScopedLock lock(&alloc_lock);
addr_t kmem_from;
Memory::GetKernelVirtualArea(&kmem_from, total);
*heap = heap_allocated;
*aux = aux_allocated - aux_leaked;
*leaked = aux_leaked;
}
// No need for locks in these three functions, since only the heap calls these
// and it already uses an internal lock, and heap_allocated will not change
// unless the heap calls ExpandHeap.

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -17,6 +17,8 @@
* Handles communication to COM serial ports.
*/
#include <sys/ioctl.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
@ -38,6 +40,7 @@
#include <sortix/kernel/thread.h>
#include "com.h"
#include "tty.h"
extern "C" unsigned char nullpage[4096];
@ -136,17 +139,30 @@ static inline bool CanWriteByte(uint16_t port)
return inport8(port + LSR) & LSR_THRE;
}
class DevCOMPort : public AbstractInode
class DevCOMPort : public TTY
{
public:
DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, uint16_t port);
DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode, uint16_t port,
const char* name);
virtual ~DevCOMPort();
virtual int ioctl(ioctx_t* ctx, int cmd, uintptr_t arg);
virtual int sync(ioctx_t* ctx);
virtual ssize_t read(ioctx_t* ctx, uint8_t* buf, size_t count);
virtual ssize_t write(ioctx_t* ctx, const uint8_t* buf, size_t count);
virtual void tty_output(const unsigned char* buffer, size_t length);
public:
bool Initialize(int interrupt);
private:
static void InterruptHandler(struct interrupt_context*, void*);
static void InterruptWorkHandler(void* context);
void OnInterrupt();
void InterruptWork();
private:
kthread_mutex_t port_lock;
struct interrupt_handler irq_registration;
struct interrupt_work interrupt_work;
struct winsize ws;
uint16_t port;
uint8_t pending_input_byte;
bool has_pending_input_byte;
@ -154,24 +170,93 @@ private:
};
DevCOMPort::DevCOMPort(dev_t dev, uid_t owner, gid_t group, mode_t mode,
uint16_t port)
uint16_t port, const char* name) : TTY(dev, ino, mode,
owner, group, name)
{
inode_type = INODE_TYPE_STREAM;
this->port = port;
this->port_lock = KTHREAD_MUTEX_INITIALIZER;
this->stat_uid = owner;
this->stat_gid = group;
this->type = S_IFCHR;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->dev = dev;
this->ino = (ino_t) this;
this->has_pending_input_byte = false;
interrupt_work.handler = InterruptWorkHandler;
interrupt_work.context = this;
}
DevCOMPort::~DevCOMPort()
{
}
bool DevCOMPort::Initialize(int interrupt)
{
uint8_t interrupts = 1;
// TODO: This was 9600.
tio.c_ispeed = B19200;
tio.c_ospeed = B19200;
uint16_t divisor = 115200 / tio.c_ispeed;
outport8(port + FCR, 0);
outport8(port + LCR, LCR_DLAB);
outport8(port + DLL, divisor & 0xFF);
outport8(port + DLM, divisor >> 8);
outport8(port + LCR, LCR_WLEN8); // 8n1
outport8(port + MCR, 0x1 /* DTR */ | 0x2 /* RTS */);
outport8(port + IER, interrupts);
irq_registration.handler = DevCOMPort::InterruptHandler;
irq_registration.context = this;
Interrupt::RegisterHandler(interrupt, &irq_registration);
return true;
}
void DevCOMPort::InterruptHandler(struct interrupt_context*, void* user)
{
((DevCOMPort*) user)->OnInterrupt();
}
void DevCOMPort::OnInterrupt()
{
if ( !IsLineReady(port) )
return;
Interrupt::ScheduleWork(&interrupt_work);
}
void DevCOMPort::InterruptWorkHandler(void* context)
{
((DevCOMPort*) context)->InterruptWork();
}
void DevCOMPort::InterruptWork()
{
ScopedLock lock1(&termlock);
ScopedLock lock2(&port_lock);
while ( IsLineReady(port) )
{
unsigned char byte = inport8(port + RXR);
if ( tio.c_cflag & CREAD )
ProcessByte(byte);
}
}
int DevCOMPort::ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
{
ScopedLock lock(&termlock);
if ( hungup )
return errno = EIO, -1;
if ( cmd == TIOCGWINSZ )
{
struct winsize* user_ws = (struct winsize*) arg;
if ( !ctx->copy_to_dest(user_ws, &ws, sizeof(ws)) )
return -1;
return 0;
}
else if ( cmd == TIOCSWINSZ )
{
const struct winsize* user_ws = (const struct winsize*) arg;
if ( !ctx->copy_from_src(&ws, user_ws, sizeof(ws)) )
return -1;
winch();
return 0;
}
lock.Reset();
return TTY::ioctl(ctx, cmd, arg);
}
int DevCOMPort::sync(ioctx_t* /*ctx*/)
{
ScopedLock lock(&port_lock);
@ -179,57 +264,9 @@ int DevCOMPort::sync(ioctx_t* /*ctx*/)
return 0;
}
ssize_t DevCOMPort::read(ioctx_t* ctx, uint8_t* dest, size_t count)
void DevCOMPort::tty_output(const unsigned char* buffer, size_t length)
{
ScopedLock lock(&port_lock);
for ( size_t i = 0; i < count; i++ )
{
unsigned long attempt = 0;
while ( !has_pending_input_byte && !IsLineReady(port) )
{
attempt++;
if ( attempt <= 10 )
continue;
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
{
kthread_mutex_unlock(&port_lock);
kthread_yield();
kthread_mutex_lock(&port_lock);
continue;
}
if ( i )
return (ssize_t) i;
if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1;
if ( Signal::IsPending() )
return errno = EINTR, -1;
kthread_mutex_unlock(&port_lock);
kthread_yield();
kthread_mutex_lock(&port_lock);
}
uint8_t value = has_pending_input_byte ?
pending_input_byte :
inport8(port + RXR);
if ( !ctx->copy_to_dest(dest + i, &value, sizeof(value)) )
{
has_pending_input_byte = true;
pending_input_byte = value;
return i ? (ssize_t) i : -1;
}
has_pending_input_byte = false;
}
return (ssize_t) count;
}
ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
{
ScopedLock lock(&port_lock);
for ( size_t i = 0; i < count; i++ )
for ( size_t i = 0; i < length; i++ )
{
unsigned long attempt = 0;
while ( !CanWriteByte(port) )
@ -237,7 +274,7 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
attempt++;
if ( attempt <= 10 )
continue;
if ( attempt <= 15 && !(ctx->dflags & O_NONBLOCK) )
if ( attempt <= 15 )
{
kthread_mutex_unlock(&port_lock);
kthread_yield();
@ -245,20 +282,16 @@ ssize_t DevCOMPort::write(ioctx_t* ctx, const uint8_t* src, size_t count)
continue;
}
if ( i )
return (ssize_t) i;
if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1;
return;
// TODO: This is problematic.
if ( Signal::IsPending() )
return errno = EINTR, -1;
{
errno = EINTR;
return;
}
}
uint8_t value;
if ( !ctx->copy_from_src(&value, src + i, sizeof(value)) )
return i ? (ssize_t) i : -1;
outport8(port + TXR, value);
outport8(port + TXR, buffer[i]);
}
return (ssize_t) count;
}
static Ref<DevCOMPort> com_devices[1 + NUM_COM_PORTS];
@ -282,21 +315,6 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
ioctx_t ctx; SetupKernelIOCtx(&ctx);
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
{
uint16_t port = com_ports[i];
if ( !port )
continue;
uint8_t interrupts = 0;
outport8(port + FCR, 0);
outport8(port + LCR, 0x80);
outport8(port + DLL, 0xC);
outport8(port + DLM, 0x0);
outport8(port + LCR, 0x3); // 8n1
outport8(port + MCR, 0x3); // DTR + RTS
outport8(port + IER, interrupts);
}
for ( size_t i = 1; i <= NUM_COM_PORTS; i++ )
{
if ( !com_ports[i] )
@ -304,13 +322,17 @@ void Init(const char* devpath, Ref<Descriptor> slashdev)
com_devices[i] = Ref<DevCOMPort>();
continue;
}
com_devices[i] = Ref<DevCOMPort>(new DevCOMPort(slashdev->dev, 0, 0, 0660, com_ports[i]));
if ( !com_devices[i] )
char ttyname[TTY_NAME_MAX+1];
snprintf(ttyname, sizeof(ttyname), "com%zu", i);
Ref<DevCOMPort> com(
new DevCOMPort(slashdev->dev, 0, 0, 0660, com_ports[i], ttyname));
if ( !com )
PanicF("Unable to allocate device for COM port %zu", i);
char name[3 + sizeof(size_t) * 3];
snprintf(name, sizeof(name), "com%zu", i);
if ( LinkInodeInDir(&ctx, slashdev, name, com_devices[i]) != 0 )
PanicF("Unable to link %s/%s to COM port driver.", devpath, name);
com_devices[i] = com;
int interrupt = i == 1 || i == 3 ? Interrupt::IRQ4 : Interrupt::IRQ3;
com->Initialize(interrupt);
if ( LinkInodeInDir(&ctx, slashdev, ttyname, com) != 0 )
PanicF("Unable to link %s/%s to COM port driver.", devpath, ttyname);
}
}

View file

@ -679,9 +679,9 @@ Ref<Descriptor> Descriptor::open(ioctx_t* ctx, const char* filename, int flags,
mode_t mode)
{
Process* process = CurrentProcess();
kthread_mutex_lock(&process->idlock);
kthread_mutex_lock(&process->id_lock);
mode &= ~process->umask;
kthread_mutex_unlock(&process->idlock);
kthread_mutex_unlock(&process->id_lock);
if ( !filename[0] )
return errno = ENOENT, Ref<Descriptor>();
@ -840,9 +840,9 @@ Ref<Descriptor> Descriptor::open_elem(ioctx_t* ctx, const char* filename,
int Descriptor::mkdir(ioctx_t* ctx, const char* filename, mode_t mode)
{
Process* process = CurrentProcess();
kthread_mutex_lock(&process->idlock);
kthread_mutex_lock(&process->id_lock);
mode &= ~process->umask;
kthread_mutex_unlock(&process->idlock);
kthread_mutex_unlock(&process->id_lock);
char* final;
Ref<Descriptor> dir = OpenDirContainingPath(ctx, Ref<Descriptor>(this),

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -49,11 +49,17 @@ static unsigned long AllocateDiskNumber()
return InterlockedIncrement(&next_disk_number).o;
}
static void sleep_400_nanoseconds()
static void sleep_400_nanoseconds(uint16_t port_base)
{
// TODO: The clock granularity of 10 ms slows down the early boot.
#if 0
struct timespec delay = timespec_make(0, 400);
Clock* clock = Time::GetClock(CLOCK_BOOTTIME);
clock->SleepDelay(delay);
#else
for ( int i = 0; i < 14; i++ )
inport8(port_base + REG_STATUS);
#endif
}
Channel::Channel()
@ -105,7 +111,7 @@ void Channel::SelectDrive(unsigned int drive_index) // hw_lock locked
outport8(port_base + REG_DRIVE_SELECT, value);
//outport8(port_control, value); // TODO: Or is it port_control we use?
sleep_400_nanoseconds();
sleep_400_nanoseconds(port_base);
// TODO: Do we need to wait for non-busy now? Can this operation fail?
@ -229,7 +235,7 @@ bool Channel::Initialize(Ref<Descriptor> dev, const char* devpath)
busmaster_base = busmasterbar.addr() + 8 * channel_index;
current_drive = (unsigned int) -1; // We don't know.
current_drive = (inport8(port_base + REG_DRIVE_SELECT) >> 4) & 1;
for ( unsigned int i = 0; i < 2; i++ )
{

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2016, 2018, 2021 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2018, 2021, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -56,11 +56,17 @@ static void copy_ata_string(char* dest, const char* src, size_t length)
dest[length] = '\0';
}
static void sleep_400_nanoseconds()
static void sleep_400_nanoseconds(uint16_t port_base)
{
// TODO: The clock granularity of 10 ms slows down the early boot.
#if 0
struct timespec delay = timespec_make(0, 400);
Clock* clock = Time::GetClock(CLOCK_BOOTTIME);
clock->SleepDelay(delay);
#else
for ( int i = 0; i < 14; i++ )
inport8(port_base + REG_STATUS);
#endif
}
Port::Port(Channel* channel, unsigned int port_index)
@ -176,7 +182,7 @@ retry_identify_packet:
outport8(channel->port_base + REG_COMMAND,
is_packet_interface ? CMD_IDENTIFY_PACKET : CMD_IDENTIFY);
sleep_400_nanoseconds();
sleep_400_nanoseconds(channel->port_base);
// TODO: The status polling logic should be double-checked against some
// formal specification telling how this should properly be done.

View file

@ -29,14 +29,14 @@ namespace Sortix {
uid_t sys_getuid()
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
return process->uid;
}
int sys_setuid(uid_t uid)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
// TODO: Implement security checks in many place across the operating system
// and until then allow anyone to do this to not pretend to be secure.
process->uid = uid;
@ -47,14 +47,14 @@ int sys_setuid(uid_t uid)
gid_t sys_getgid()
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
return process->gid;
}
int sys_setgid(gid_t gid)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
// TODO: Implement security checks in many place across the operating system
// and until then allow anyone to do this to not pretend to be secure.
process->gid = gid;
@ -65,14 +65,14 @@ int sys_setgid(gid_t gid)
uid_t sys_geteuid()
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
return process->euid;
}
int sys_seteuid(uid_t euid)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
// TODO: Implement security checks in many place across the operating system
// and until then allow anyone to do this to not pretend to be secure.
process->euid = euid;
@ -82,14 +82,14 @@ int sys_seteuid(uid_t euid)
gid_t sys_getegid()
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
return process->egid;
}
int sys_setegid(gid_t egid)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
// TODO: Implement security checks in many place across the operating system
// and until then allow anyone to do this to not pretend to be secure.
process->egid = egid;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2016, 2017, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -39,6 +39,7 @@
#define TIOCGNAME __IOCTL(6, __IOCTL_TYPE_PTR)
#define TIOCGPTN __IOCTL(7, __IOCTL_TYPE_PTR)
#define TIOCGDISPLAYS __IOCTL(8, __IOCTL_TYPE_PTR)
#define TIOCUCTTY __IOCTL(9, __IOCTL_TYPE_INT)
#define IOC_TYPE(x) ((x) >> 0 & 0xFF)
#define IOC_TYPE_BLOCK_DEVICE 1

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013 Jonas 'Sortie' Termansen.
* Copyright (c) 2013, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -39,6 +39,7 @@ void ShrinkHeap(size_t decrease);
addr_t GetHeapLower();
addr_t GetHeapUpper();
size_t GetHeapSize();
void KernelAddressStatistics(size_t* heap, size_t* aux, size_t* leaked, size_t* total);
} // namespace Sortix

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2013, 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 2013, 2014, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -36,4 +36,35 @@ typedef uintptr_t addr_t;
#define CPU X64
#endif
#ifdef __TRACE_ALLOCATION_SITES
struct kernel_allocation_site
{
struct __allocation_site allocation_site;
struct kernel_allocation_site* next;
bool registered;
};
extern struct kernel_allocation_site* first_kernel_allocation_site;
#undef ALLOCATION_SITE
#define ALLOCATION_SITE \
({ \
static struct kernel_allocation_site site = \
{ { __FILE__, __LINE__, __func__, 0, 0 }, NULL, false }; \
if ( !site.registered ) \
{ \
site.registered = true; \
site.next = first_kernel_allocation_site; \
first_kernel_allocation_site = &site; \
} \
&site.allocation_site; \
})
#include <stddef.h>
void* operator new(size_t size, struct __allocation_site* allocation_site);
void* operator new[](size_t size, struct __allocation_site* allocation_site);
#define new new (ALLOCATION_SITE)
#endif
#endif

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2016, 2021 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2021, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -66,17 +66,17 @@ public:
pid_t pid;
public:
kthread_mutex_t nicelock;
kthread_mutex_t nice_lock;
int nice;
public:
kthread_mutex_t idlock;
kthread_mutex_t id_lock;
uid_t uid, euid;
gid_t gid, egid;
mode_t umask;
private:
kthread_mutex_t ptrlock;
kthread_mutex_t ptr_lock;
Ref<Descriptor> tty;
Ref<Descriptor> root;
Ref<Descriptor> cwd;
@ -112,29 +112,34 @@ public:
public:
Process* parent;
Process* prevsibling;
Process* nextsibling;
Process* firstchild;
Process* zombiechild;
Process* prev_sibling;
Process* next_sibling;
Process* first_child;
Process* zombie_child;
Process* group;
Process* groupprev;
Process* groupnext;
Process* groupfirst;
Process* group_prev;
Process* group_next;
Process* group_first;
Process* session;
Process* sessionprev;
Process* sessionnext;
Process* sessionfirst;
kthread_mutex_t childlock;
kthread_mutex_t parentlock;
kthread_cond_t zombiecond;
bool iszombie;
bool nozombify;
Process* session_prev;
Process* session_next;
Process* session_first;
Process* init;
Process* init_prev;
Process* init_next;
Process* init_first;
kthread_mutex_t child_lock;
kthread_mutex_t parent_lock;
kthread_cond_t zombie_cond;
bool is_zombie;
bool no_zombify;
bool limbo;
bool is_init_exiting;
int exit_code;
public:
Thread* firstthread;
kthread_mutex_t threadlock;
Thread* first_thread;
kthread_mutex_t thread_lock;
size_t threads_not_exiting_count;
bool threads_exiting;
@ -178,6 +183,7 @@ public:
int prot);
void GroupRemoveMember(Process* child);
void SessionRemoveMember(Process* child);
void InitRemoveMember(Process* child);
public:
Process* Fork();

View file

@ -41,8 +41,6 @@ void SetThreadState(Thread* thread, ThreadState state, bool wake_only = false);
void SetSignalPending(Thread* thread, unsigned long is_pending);
ThreadState GetThreadState(Thread* thread);
void SetIdleThread(Thread* thread);
void SetInitProcess(Process* init);
Process* GetInitProcess();
Process* GetKernelProcess();
void InterruptYieldCPU(struct interrupt_context* intctx, void* user);
void ThreadExitCPU(struct interrupt_context* intctx, void* user);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2016, 2021, 2022, 2023 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2021-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -97,6 +97,7 @@ int sys_getentropy(void*, size_t);
uid_t sys_geteuid(void);
gid_t sys_getgid(void);
int sys_gethostname(char*, size_t);
pid_t sys_getinit(pid_t);
size_t sys_getpagesize(void);
int sys_getpeername(int, void*, size_t*);
pid_t sys_getpgid(pid_t);
@ -151,6 +152,7 @@ int sys_setegid(gid_t);
int sys_seteuid(uid_t);
int sys_setgid(gid_t);
int sys_sethostname(const char*, size_t);
int sys_setinit(void);
int sys_setpgid(pid_t, pid_t);
int sys_setpriority(int, id_t, int);
pid_t sys_setsid(void);

View file

@ -79,8 +79,8 @@ public:
struct thread_registers registers;
size_t id;
Process* process;
Thread* prevsibling;
Thread* nextsibling;
Thread* prev_sibling;
Thread* next_sibling;
Thread* scheduler_list_prev;
Thread* scheduler_list_next;
volatile ThreadState state;
@ -88,12 +88,12 @@ public:
sigset_t signal_mask;
sigset_t saved_signal_mask;
stack_t signal_stack;
addr_t kernelstackpos;
size_t kernelstacksize;
addr_t kernel_stack_pos;
size_t kernel_stack_size;
size_t signal_count;
uintptr_t signal_single_frame;
uintptr_t signal_canary;
bool kernelstackmalloced;
bool kernel_stack_malloced;
bool pledged_destruction;
bool force_no_signals;
bool signal_single;

View file

@ -190,6 +190,8 @@
#define SYSCALL_SETDNSCONFIG 167
#define SYSCALL_FUTEX 168
#define SYSCALL_MEMUSAGE 169
#define SYSCALL_MAX_NUM 170 /* index of highest constant + 1 */
#define SYSCALL_GETINIT 170
#define SYSCALL_SETINIT 171
#define SYSCALL_MAX_NUM 172 /* index of highest constant + 1 */
#endif

View file

@ -27,7 +27,7 @@ namespace Sortix {
void SetupUserIOCtx(ioctx_t* ctx)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
ctx->uid = ctx->auth_uid = process->uid;
ctx->gid = ctx->auth_gid = process->gid;
ctx->copy_to_dest = CopyToUser;
@ -39,7 +39,7 @@ void SetupUserIOCtx(ioctx_t* ctx)
void SetupKernelIOCtx(ioctx_t* ctx)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
ctx->uid = ctx->auth_uid = process->uid;
ctx->gid = ctx->auth_gid = process->gid;
ctx->copy_to_dest = CopyToKernel;

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2018, 2021-2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2018, 2021-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -103,6 +103,8 @@
#include "x86-family/vbox.h"
#endif
struct kernel_allocation_site* first_kernel_allocation_site = NULL;
// Keep the stack size aligned with $CPU/boot.s
const size_t STACK_SIZE = 64*1024;
extern "C" { __attribute__((aligned(16))) size_t stack[STACK_SIZE / sizeof(size_t)]; }
@ -231,6 +233,13 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
if ( !(kernel_options = strdup(cmdline ? cmdline : "")) )
Panic("Failed to allocate kernel command line");
#if defined(__i386__) || defined(__x86_64__)
// TODO: Detect EFI.
kernel_firmware = "bios";
#else
#warning "Name your system firmware here"
kernel_firmware = "unknown";
#endif
int argmax = 1;
argv = new char*[argmax + 1];
@ -286,6 +295,20 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
enable_network_drivers = true;
else if ( !strcmp(arg, "--no-random-seed") )
no_random_seed = true;
else if ( !strncmp(arg, "--firmware=", strlen("--firmware=")) )
{
const char* firmware = arg + strlen("--firmware=");
#if defined(__i386__) || defined(__x86_64__)
if ( !strcmp(firmware, "bios") || !strcmp(firmware, "pc") )
kernel_firmware = "bios";
else if ( !strcmp(firmware, "efi") )
kernel_firmware = "efi";
else
#endif
{
PanicF("Unsupported firmware option: %s", firmware);
}
}
else
{
Log::PrintF("\r\e[J");
@ -351,13 +374,17 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
ptable.Reset();
system->addrspace = Memory::GetAddressSpace();
system->group = system;
system->groupprev = NULL;
system->groupnext = NULL;
system->groupfirst = system;
system->group_prev = NULL;
system->group_next = NULL;
system->group_first = system;
system->session = system;
system->sessionprev = NULL;
system->sessionnext = NULL;
system->sessionfirst = system;
system->session_prev = NULL;
system->session_next = NULL;
system->session_first = system;
system->init = NULL;
system->init_prev = NULL;
system->init_next = NULL;
system->init_first = NULL;
if ( !(system->program_image_path = String::Clone("<kernel process>")) )
Panic("Unable to clone string for system process name");
@ -369,10 +396,10 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
Thread* idlethread = new Thread();
idlethread->name = "idle";
idlethread->process = system;
idlethread->kernelstackpos = (addr_t) stack;
idlethread->kernelstacksize = STACK_SIZE;
idlethread->kernelstackmalloced = false;
system->firstthread = idlethread;
idlethread->kernel_stack_pos = (addr_t) stack;
idlethread->kernel_stack_size = STACK_SIZE;
idlethread->kernel_stack_malloced = false;
system->first_thread = idlethread;
system->threads_not_exiting_count = 1;
Scheduler::SetIdleThread(idlethread);
@ -640,19 +667,23 @@ static void BootThread(void* /*user*/)
kthread_mutex_lock(&process_family_lock);
Process* kernel_process = CurrentProcess();
init->parent = kernel_process;
init->nextsibling = kernel_process->firstchild;
init->prevsibling = NULL;
if ( kernel_process->firstchild )
kernel_process->firstchild->prevsibling = init;
kernel_process->firstchild = init;
init->next_sibling = kernel_process->first_child;
init->prev_sibling = NULL;
if ( kernel_process->first_child )
kernel_process->first_child->prev_sibling = init;
kernel_process->first_child = init;
init->group = init;
init->groupprev = NULL;
init->groupnext = NULL;
init->groupfirst = init;
init->group_prev = NULL;
init->group_next = NULL;
init->group_first = init;
init->session = init;
init->sessionprev = NULL;
init->sessionnext = NULL;
init->sessionfirst = init;
init->session_prev = NULL;
init->session_next = NULL;
init->session_first = init;
init->init = init;
init->init_prev = NULL;
init->init_next = NULL;
init->init_first = init;
kthread_mutex_unlock(&process_family_lock);
// TODO: Why don't we fork from pid=0 and this is done for us?
@ -662,7 +693,6 @@ static void BootThread(void* /*user*/)
mtable.Reset();
init->BootstrapDirectories(droot);
init->addrspace = initaddrspace;
Scheduler::SetInitProcess(init);
Thread* initthread = RunKernelThread(init, InitThread, NULL, "main");
if ( !initthread )

View file

@ -25,6 +25,7 @@
#include <sortix/kernel/syscall.h>
#include "kernelinfo.h"
#include "net/tcp.h"
#ifndef VERSIONSTR
#define VERSIONSTR "unknown"
@ -33,20 +34,17 @@
namespace Sortix {
char* kernel_options;
const char* kernel_firmware;
static const char* KernelInfo(const char* req)
{
if ( strcmp(req, "name") == 0 ) { return BRAND_KERNEL_NAME; }
if ( strcmp(req, "version") == 0 ) { return VERSIONSTR; }
if ( strcmp(req, "tagline") == 0 ) { return BRAND_RELEASE_TAGLINE; }
if ( strcmp(req, "options") == 0 ) { return kernel_options; }
if ( strcmp(req, "builddate") == 0 ) { return __DATE__; }
if ( strcmp(req, "buildtime") == 0 ) { return __TIME__; }
#if defined(__i386__) || defined(__x86_64__)
if ( strcmp(req, "firmware") == 0 ) { return "bios"; }
#else
#warning "Name your system firmware here"
#endif
if ( strcmp(req, "name") == 0 ) return BRAND_KERNEL_NAME;
if ( strcmp(req, "version") == 0 ) return VERSIONSTR;
if ( strcmp(req, "tagline") == 0 ) return BRAND_RELEASE_TAGLINE;
if ( strcmp(req, "options") == 0 ) return kernel_options;
if ( strcmp(req, "builddate") == 0 ) return __DATE__;
if ( strcmp(req, "buildtime") == 0 ) return __TIME__;
if ( strcmp(req, "firmware") == 0 ) return kernel_firmware;
return NULL;
}
@ -55,6 +53,77 @@ ssize_t sys_kernelinfo(const char* user_req, char* user_resp, size_t resplen)
char* req = GetStringFromUser(user_req);
if ( !req )
return -1;
// DEBUG
if ( !strcmp(req, "virtual-usage") )
{
delete[] req;
size_t heap;
size_t aux;
size_t leaked;
size_t total;
KernelAddressStatistics(&heap, &aux, &leaked, &total);
char str[4 * (20 + 3 * sizeof(size_t) + 1 + 8)];
snprintf(str, sizeof(str),
"%20zu B heap\n%20zu B aux\n%20zu B leaked\n%20zu B total",
heap, aux, leaked, total);
size_t stringlen = strlen(str);
if ( resplen < stringlen + 1 )
return errno = ERANGE, (ssize_t) stringlen;
if ( !CopyToUser(user_resp, str, sizeof(char) * (stringlen + 1)) )
return -1;
return 0;
}
// DEBUG
if ( !strcmp(req, "tcp") )
{
delete[] req;
return TCP::Info(user_resp, resplen);
}
#ifdef __TRACE_ALLOCATION_SITES
if ( !strcmp(req, "allocations") )
{
delete[] req;
bool exhausted = false;
size_t total_needed = 0;
for ( struct kernel_allocation_site* site = first_kernel_allocation_site;
site;
site = site->next )
{
char str[256];
snprintf(str, sizeof(str), "%20zu B %zu allocations %s:%zu %s %c",
site->allocation_site.current_size,
site->allocation_site.allocations,
site->allocation_site.file,
site->allocation_site.line,
site->allocation_site.function,
site->next ? '\n' : '\0');
size_t stringlen = strlen(str);
total_needed += stringlen;
if ( exhausted )
continue;
if ( resplen < stringlen )
{
exhausted = true;
continue;
}
if ( !CopyToUser(user_resp, str, sizeof(char) * stringlen) )
return -1;
user_resp += stringlen;
resplen -= stringlen;
}
if ( !exhausted && !resplen )
exhausted = true;
if ( !exhausted )
{
char zero = '\0';
if ( !CopyToUser(user_resp, &zero, 1) )
return -1;
}
if ( exhausted )
return errno = ERANGE, (ssize_t) total_needed;
return 0;
}
#endif
const char* str = KernelInfo(req);
delete[] req;
if ( !str )

View file

@ -23,6 +23,7 @@
namespace Sortix {
extern char* kernel_options;
extern const char* kernel_firmware;
} // namespace Sortix

View file

@ -258,9 +258,9 @@ void kthread_exit()
// only threads in this process, except the initial thread. Otherwise more
// threads may appear, and we can't conclude whether this is the last thread
// in the process to exit.
kthread_mutex_lock(&process->threadlock);
kthread_mutex_lock(&process->thread_lock);
bool is_last_to_exit = --process->threads_not_exiting_count == 0;
kthread_mutex_unlock(&process->threadlock);
kthread_mutex_unlock(&process->thread_lock);
// All other threads in the process have committed to exiting, though they
// might not have exited yet. However, we know they are only running the
// below code that schedules thread termination. It's therefore safe to run

View file

@ -50,6 +50,7 @@ LineBuffer::~LineBuffer()
bool LineBuffer::Push(uint32_t unicode)
{
// TODO: An infinite line buffer seems like a bad idea.
// Check if we need to allocate or resize the circular queue.
if ( bufferused == bufferlength )
{

View file

@ -52,13 +52,11 @@ namespace NetFS {
class Manager;
class StreamSocket;
class Manager : public AbstractInode
class Manager : public Refcountable
{
public:
Manager(uid_t owner, gid_t group, mode_t mode);
virtual ~Manager() { }
virtual Ref<Inode> open(ioctx_t* ctx, const char* filename, int flags,
mode_t mode);
Manager();
virtual ~Manager();
public:
bool Bind(StreamSocket* socket, struct sockaddr_un* addr, size_t addrsize);
@ -114,6 +112,7 @@ public:
public: /* For use by Manager. */
PollChannel accept_poll_channel;
Ref<Manager> manager;
Ref<Descriptor> root;
PipeEndpoint incoming;
PipeEndpoint outgoing;
StreamSocket* prev_socket;
@ -189,6 +188,7 @@ StreamSocket::StreamSocket(uid_t owner, gid_t group, mode_t mode,
this->is_connected = false;
this->is_refused = false;
this->manager = manager;
this->root = CurrentProcess()->GetRoot();
this->socket_lock = KTHREAD_MUTEX_INITIALIZER;
this->pending_cond = KTHREAD_COND_INITIALIZER;
this->accepted_cond = KTHREAD_COND_INITIALIZER;
@ -476,20 +476,19 @@ int StreamSocket::getsockname(ioctx_t* ctx, uint8_t* addr, size_t* addrsize)
return 0;
}
Manager::Manager(uid_t owner, gid_t group, mode_t mode)
Manager::Manager()
{
inode_type = INODE_TYPE_UNKNOWN;
dev = (dev_t) this;
ino = 0;
this->type = S_IFDIR;
this->stat_uid = owner;
this->stat_gid = group;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->manager_lock = KTHREAD_MUTEX_INITIALIZER;
this->first_server = NULL;
this->last_server = NULL;
}
Manager::~Manager()
{
assert(!first_server);
assert(!last_server);
}
static int CompareAddress(const struct sockaddr_un* a,
const struct sockaddr_un* b)
{
@ -498,8 +497,10 @@ static int CompareAddress(const struct sockaddr_un* a,
StreamSocket* Manager::LookupServer(struct sockaddr_un* address)
{
Ref<Descriptor> root = CurrentProcess()->GetRoot();
for ( StreamSocket* iter = first_server; iter; iter = iter->next_socket )
if ( CompareAddress(iter->name, address) == 0 )
if ( CompareAddress(iter->name, address) == 0 &&
iter->root->dev == root->dev && iter->root->ino == root->ino )
return iter;
return NULL;
}
@ -664,24 +665,11 @@ bool Manager::Connect(StreamSocket* socket,
return true;
}
// TODO: Support a poll method in Manager.
Ref<Inode> Manager::open(ioctx_t* /*ctx*/, const char* filename,
int /*flags*/, mode_t /*mode*/)
{
if ( !strcmp(filename, "stream") )
{
StreamSocket* socket = new StreamSocket(0, 0, 0666, Ref<Manager>(this));
return Ref<StreamSocket>(socket);
}
return errno = ENOENT, Ref<Inode>(NULL);
}
static Ref<Manager> manager;
void Init()
{
manager = Ref<Manager>(new Manager(0, 0, 0600));
manager = Ref<Manager>(new Manager());
}
Ref<Inode> Socket(int type, int protocol)

View file

@ -202,10 +202,10 @@ PingSocket::PingSocket(int af)
dev = (dev_t) this;
ino = (ino_t) this;
type = S_IFSOCK;
kthread_mutex_lock(&process->idlock);
kthread_mutex_lock(&process->id_lock);
stat_uid = process->uid;
stat_gid = process->gid;
kthread_mutex_unlock(&process->idlock);
kthread_mutex_unlock(&process->id_lock);
stat_mode = 0600 | this->type;
supports_iovec = true;
socket_lock = KTHREAD_MUTEX_INITIALIZER;

View file

@ -36,6 +36,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <assert.h>
#include <endian.h>
#include <errno.h>
@ -120,6 +121,9 @@ static kthread_mutex_t tcp_lock = KTHREAD_MUTEX_INITIALIZER;
static TCPSocket** bindings_v4;
static TCPSocket** bindings_v6;
static TCPSocket* all_first_socket;
static TCPSocket* all_last_socket;
void Init()
{
if ( !(bindings_v4 = new TCPSocket*[65536]) ||
@ -218,6 +222,7 @@ public:
int getsockname(ioctx_t* ctx, uint8_t* addr, size_t* addrsize);
public:
size_t Describe(char* buf, size_t buflen);
void Unreference();
void ProcessPacket(Ref<Packet> pkt, union tcp_sockaddr* pkt_src,
union tcp_sockaddr* pkt_dst);
@ -278,6 +283,12 @@ public:
// The listening socket this socket is in the listening queue for.
TCPSocket* connecting_parent;
// DEBUG: The previous socket of all sockets.
TCPSocket* all_prev_socket;
// DEBUG: The next socket of all sockets.
TCPSocket* all_next_socket;
// Condition variable that is signaled when new data can be received.
kthread_cond_t receive_cond;
@ -461,7 +472,7 @@ void TCPSocket__OnTimer(Clock* /*clock*/, Timer* /*timer*/, void* user)
((TCPSocket*) user)->OnTimer();
}
TCPSocket::TCPSocket(int af)
TCPSocket::TCPSocket(int af) // DEBUG: tcp_lock taken
{
prev_socket = NULL;
next_socket = NULL;
@ -517,9 +528,15 @@ TCPSocket::TCPSocket(int af)
shutdown_receive = false;
memset(incoming, 0, sizeof(incoming));
memset(outgoing, 0, sizeof(outgoing));
// DEBUG
all_prev_socket = all_last_socket;
all_next_socket = NULL;
(all_last_socket ?
all_last_socket->all_next_socket : all_first_socket) = this;
all_last_socket = this;
}
TCPSocket::~TCPSocket()
TCPSocket::~TCPSocket() // DEBUG: tcp_lock taken
{
assert(state == TCP_STATE_CLOSED);
assert(!bound);
@ -539,6 +556,52 @@ TCPSocket::~TCPSocket()
receive_queue = packet->next;
packet->next.Reset();
}
// DEBUG
(all_prev_socket ?
all_prev_socket->all_next_socket : all_first_socket) = all_next_socket;
(all_next_socket ?
all_next_socket->all_prev_socket : all_last_socket) = all_prev_socket;
all_prev_socket = NULL;
all_next_socket = NULL;
}
// DEBUG
size_t TCPSocket::Describe(char* buf, size_t buflen) // tcp_lock taken
{
const char* const STATE_NAMES[] =
{
"CLOSED",
"LISTEN",
"SYN_SENT",
"SYN_RECV",
"ESTAB",
"FIN_WAIT_1",
"FIN_WAIT_2",
"CLOSE_WAIT",
"CLOSING",
"LAST_ACK",
"TIME_WAIT",
};
const char* state_name = STATE_NAMES[state];
char local_str[INET_ADDRSTRLEN];
char remote_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &local.in.sin_addr, local_str, sizeof(local_str));
inet_ntop(AF_INET, &remote.in.sin_addr, remote_str, sizeof(remote_str));
char timeout[64] = "none";
if ( timer_armed )
{
struct itimerspec its;
timer.Get(&its);
snprintf(timeout, sizeof(timeout), "%ji.%09li",
(intmax_t) its.it_value.tv_sec, its.it_value.tv_nsec);
}
return snprintf(buf, buflen,
"%s %s %u -> %s %u"
" timeout=%s resends=%u sockerr=%i transmit=%i refed=%i\n",
state_name, local_str, be16toh(local.in.sin_port),
remote_str, be16toh(remote.in.sin_port), timeout,
retransmissions, sockerr, transmit_scheduled,
is_referenced);
}
void TCPSocket::Unreference()
@ -2354,10 +2417,10 @@ TCPSocketNode::TCPSocketNode(TCPSocket* socket)
dev = (dev_t) this;
ino = (ino_t) this;
type = S_IFSOCK;
kthread_mutex_lock(&process->idlock);
kthread_mutex_lock(&process->id_lock);
stat_uid = process->uid;
stat_gid = process->gid;
kthread_mutex_unlock(&process->idlock);
kthread_mutex_unlock(&process->id_lock);
stat_mode = 0600 | this->type;
}
@ -2558,6 +2621,7 @@ Ref<Inode> Socket(int af)
{
if ( !IsSupportedAddressFamily(af) )
return errno = EAFNOSUPPORT, Ref<Inode>(NULL);
ScopedLock lock(&tcp_lock); // DEBUG
TCPSocket* socket = new TCPSocket(af);
if ( !socket )
return Ref<Inode>();
@ -2567,5 +2631,45 @@ Ref<Inode> Socket(int af)
return result;
}
// DEBUG
ssize_t Info(char* user_resp, size_t resplen)
{
ScopedLock lock(&tcp_lock); // DEBUG
bool exhausted = false;
size_t total_needed = 0;
for ( TCPSocket* socket = all_first_socket;
socket;
socket = socket->all_next_socket )
{
char str[256];
size_t stringlen = socket->Describe(str, sizeof(str));
if ( !socket->all_next_socket && stringlen )
stringlen--;
total_needed += stringlen;
if ( exhausted )
continue;
if ( resplen < stringlen )
{
exhausted = true;
continue;
}
if ( !CopyToUser(user_resp, str, sizeof(char) * stringlen) )
return -1;
user_resp += stringlen;
resplen -= stringlen;
}
if ( !exhausted && !resplen )
exhausted = true;
if ( !exhausted )
{
char zero = '\0';
if ( !CopyToUser(user_resp, &zero, 1) )
return -1;
}
if ( exhausted )
return errno = ERANGE, (ssize_t) total_needed;
return 0;
}
} // namespace TCP
} // namespace Sortix

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2016, 2017 Jonas 'Sortie' Termansen.
* Copyright (c) 2016, 2017, 2022 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -34,6 +34,7 @@ void HandleIP(Ref<Packet> pkt,
const struct in_addr* dst,
bool dst_broadcast);
Ref<Inode> Socket(int af);
ssize_t Info(char* user_resp, size_t resplen);
} // namespace TCP
} // namespace Sortix

View file

@ -175,10 +175,10 @@ UDPSocket::UDPSocket(int af)
dev = (dev_t) this;
ino = (ino_t) this;
type = S_IFSOCK;
kthread_mutex_lock(&process->idlock);
kthread_mutex_lock(&process->id_lock);
stat_uid = process->uid;
stat_gid = process->gid;
kthread_mutex_unlock(&process->idlock);
kthread_mutex_unlock(&process->id_lock);
stat_mode = 0600 | this->type;
supports_iovec = true;
socket_lock = KTHREAD_MUTEX_INITIALIZER;

View file

@ -24,6 +24,19 @@
#warning "security: -fcheck-new might not work on clang"
#endif
#ifdef __TRACE_ALLOCATION_SITES
#undef new
void* operator new(size_t size, struct __allocation_site* allocation_site)
{
return malloc_trace(allocation_site, size);
}
void* operator new[](size_t size, struct __allocation_site* allocation_site)
{
return malloc_trace(allocation_site, size);
}
#else
void* operator new(size_t size)
{
return malloc(size);
@ -33,6 +46,7 @@ void* operator new[](size_t size)
{
return malloc(size);
}
#endif
void operator delete(void* addr)
{

View file

@ -309,6 +309,7 @@ int sys_ppoll(struct pollfd* user_fds, size_t nfds,
{
if ( !kthread_cond_wait_signal(&wakeup_cond, &wakeup_mutex) )
{
ret = -1;
errno = -EINTR;
self_woken = true;
deliver_signal = true;
@ -327,7 +328,7 @@ int sys_ppoll(struct pollfd* user_fds, size_t nfds,
timer.Detach();
}
if ( !unexpected_error )
if ( !deliver_signal && !unexpected_error )
{
int num_events = 0;
for ( size_t i = 0; i < reqs; i++ )
@ -346,6 +347,9 @@ int sys_ppoll(struct pollfd* user_fds, size_t nfds,
if ( 0 <= ret )
errno = 0;
if ( Signal::IsPending() )
ret = -1, errno = EINTR, deliver_signal = true;
if ( user_sigmask )
{
if ( !deliver_signal )

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2016, 2021-2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2021-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -73,23 +73,21 @@ kthread_mutex_t process_family_lock = KTHREAD_MUTEX_INITIALIZER;
// The system is shutting down and creation of additional processes and threads
// should be prevented. Protected by process_family_lock.
static bool is_init_exiting = false;
Process::Process()
{
program_image_path = NULL;
addrspace = 0;
pid = 0;
nicelock = KTHREAD_MUTEX_INITIALIZER;
nice_lock = KTHREAD_MUTEX_INITIALIZER;
nice = 0;
idlock = KTHREAD_MUTEX_INITIALIZER;
id_lock = KTHREAD_MUTEX_INITIALIZER;
uid = euid = 0;
gid = egid = 0;
umask = 0022;
ptrlock = KTHREAD_MUTEX_INITIALIZER;
ptr_lock = KTHREAD_MUTEX_INITIALIZER;
// tty set to null reference in the member constructor.
// root set to null reference in the member constructor.
// cwd set to null reference in the member constructor.
@ -118,26 +116,31 @@ Process::Process()
sigreturn = NULL;
parent = NULL;
prevsibling = NULL;
nextsibling = NULL;
firstchild = NULL;
zombiechild = NULL;
prev_sibling = NULL;
next_sibling = NULL;
first_child = NULL;
zombie_child = NULL;
group = NULL;
groupprev = NULL;
groupnext = NULL;
groupfirst = NULL;
group_prev = NULL;
group_next = NULL;
group_first = NULL;
session = NULL;
sessionprev = NULL;
sessionnext = NULL;
sessionfirst = NULL;
zombiecond = KTHREAD_COND_INITIALIZER;
iszombie = false;
nozombify = false;
session_prev = NULL;
session_next = NULL;
session_first = NULL;
init = NULL;
init_prev = NULL;
init_next = NULL;
init_first = NULL;
zombie_cond = KTHREAD_COND_INITIALIZER;
is_zombie = false;
no_zombify = false;
limbo = false;
is_init_exiting = false;
exit_code = -1;
firstthread = NULL;
threadlock = KTHREAD_MUTEX_INITIALIZER;
first_thread = NULL;
thread_lock = KTHREAD_MUTEX_INITIALIZER;
threads_not_exiting_count = 0;
threads_exiting = false;
@ -167,13 +170,15 @@ Process::~Process() // process_family_lock taken
if ( alarm_timer.IsAttached() )
alarm_timer.Detach();
delete[] program_image_path;
assert(!zombiechild);
assert(!zombie_child);
assert(!init);
assert(!session);
assert(!group);
assert(!parent);
assert(!sessionfirst);
assert(!groupfirst);
assert(!firstchild);
assert(!init_first);
assert(!session_first);
assert(!group_first);
assert(!first_child);
assert(!addrspace);
assert(!segments);
assert(!dtable);
@ -190,7 +195,7 @@ Process::~Process() // process_family_lock taken
void Process::BootstrapTables(Ref<DescriptorTable> dtable, Ref<MountTable> mtable)
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(!this->dtable);
assert(!this->mtable);
this->dtable = dtable;
@ -199,7 +204,7 @@ void Process::BootstrapTables(Ref<DescriptorTable> dtable, Ref<MountTable> mtabl
void Process::BootstrapDirectories(Ref<Descriptor> root)
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(!this->root);
assert(!this->cwd);
this->root = root;
@ -208,9 +213,6 @@ void Process::BootstrapDirectories(Ref<Descriptor> root)
void Process::OnLastThreadExit()
{
Process* init = Scheduler::GetInitProcess();
assert(init);
// Child processes can't be reparented away if we're init. The system is
// about to shut down, so broadcast SIGKILL every process and wait for every
// single process to exit. The operating system is finished when init has
@ -221,36 +223,43 @@ void Process::OnLastThreadExit()
// Forbid any more processes and threads from being created, so this
// loop will always terminate.
is_init_exiting = true;
kthread_mutex_lock(&ptrlock);
for ( pid_t pid = ptable->Next(0); 0 < pid; pid = ptable->Next(pid) )
Process* process = first_child;
while ( process )
{
Process* process = ptable->Get(pid);
if ( process->pid != 0 && process != init )
if ( process->pid != 0 )
process->DeliverSignal(SIGKILL);
if ( process->init == process )
process->is_init_exiting = true;
if ( process->first_child )
process = process->first_child;
while ( process && process != this && !process->next_sibling )
process = process->parent;
if ( process == this )
break;
process = process->next_sibling;
}
kthread_mutex_unlock(&ptrlock);
// NotifyChildExit always signals zombiecond for init when
// NotifyChildExit always signals zombie_cond for init when
// is_init_exiting is true.
while ( firstchild )
kthread_cond_wait(&zombiecond, &process_family_lock);
while ( first_child )
kthread_cond_wait(&zombie_cond, &process_family_lock);
}
}
void Process::OnThreadDestruction(Thread* thread)
{
assert(thread->process == this);
kthread_mutex_lock(&threadlock);
if ( thread->prevsibling )
thread->prevsibling->nextsibling = thread->nextsibling;
if ( thread->nextsibling )
thread->nextsibling->prevsibling = thread->prevsibling;
if ( thread == firstthread )
firstthread = thread->nextsibling;
if ( firstthread )
firstthread->prevsibling = NULL;
thread->prevsibling = thread->nextsibling = NULL;
bool threadsleft = firstthread;
kthread_mutex_unlock(&threadlock);
kthread_mutex_lock(&thread_lock);
if ( thread->prev_sibling )
thread->prev_sibling->next_sibling = thread->next_sibling;
if ( thread->next_sibling )
thread->next_sibling->prev_sibling = thread->prev_sibling;
if ( thread == first_thread )
first_thread = thread->next_sibling;
if ( first_thread )
first_thread->prev_sibling = NULL;
thread->prev_sibling = thread->next_sibling = NULL;
bool threadsleft = first_thread;
kthread_mutex_unlock(&thread_lock);
// We are called from the threads destructor, let it finish before we
// we handle the situation by killing ourselves.
@ -266,7 +275,7 @@ void Process__AfterLastThreadExit(void* user)
void Process::ScheduleDeath()
{
// All our threads must have exited at this point.
assert(!firstthread);
assert(!first_thread);
Worker::Schedule(Process__AfterLastThreadExit, this);
}
@ -275,7 +284,7 @@ void Process::ScheduleDeath()
// process after this call as another thread may garbage collect it.
void Process::AbortConstruction()
{
nozombify = true;
no_zombify = true;
ScheduleDeath();
}
@ -302,7 +311,7 @@ void Process::LastPrayer()
{
assert(this);
// This must never be called twice.
assert(!iszombie);
assert(!is_zombie);
// This must be called from a thread using another address space as the
// address space of this process is about to be destroyed.
@ -310,7 +319,7 @@ void Process::LastPrayer()
assert(curthread->process != this);
// This can't be called if the process is still alive.
assert(!firstthread);
assert(!first_thread);
// Disarm and detach all the timers in the process.
DeleteTimers();
@ -339,45 +348,43 @@ void Process::LastPrayer()
ScopedLock family_lock(&process_family_lock);
iszombie = true;
is_zombie = true;
// Init is nice and will gladly raise our orphaned children and zombies.
Process* init = Scheduler::GetInitProcess();
assert(init);
// Child processes can't be reparented away if we're init. OnLastThreadExit
// must have already killed all the child processes and prevented more from
// being created.
assert(init);
if ( init == this )
{
assert(is_init_exiting);
assert(!firstchild);
assert(!first_child);
}
while ( firstchild )
while ( first_child )
{
Process* process = firstchild;
firstchild = process->nextsibling;
Process* process = first_child;
first_child = process->next_sibling;
process->parent = init;
process->prevsibling = NULL;
process->nextsibling = init->firstchild;
if ( init->firstchild )
init->firstchild->prevsibling = process;
init->firstchild = process;
process->nozombify = true;
process->prev_sibling = NULL;
process->next_sibling = init->first_child;
if ( init->first_child )
init->first_child->prev_sibling = process;
init->first_child = process;
process->no_zombify = true;
}
// Since we have no more children (they are with init now), we don't
// have to worry about new zombie processes showing up, so just collect
// those that are left. Then we satisfiy the invariant !zombiechild that
// those that are left. Then we satisfiy the invariant !zombie_child that
// applies on process termination.
while ( zombiechild )
while ( zombie_child )
{
Process* zombie = zombiechild;
zombiechild = zombie->nextsibling;
zombie->nextsibling = NULL;
if ( zombiechild )
zombiechild->prevsibling = NULL;
zombie->nozombify = true;
Process* zombie = zombie_child;
zombie_child = zombie->next_sibling;
zombie->next_sibling = NULL;
if ( zombie_child )
zombie_child->prev_sibling = NULL;
zombie->no_zombify = true;
zombie->WaitedFor();
}
// Remove ourself from our process group.
@ -386,8 +393,11 @@ void Process::LastPrayer()
// Remove ourself from our session.
if ( session )
session->SessionRemoveMember(this);
// Remove ourself from our init.
if ( init )
init->InitRemoveMember(this);
bool zombify = !nozombify;
bool zombify = !no_zombify;
// This class instance will be destroyed by our parent process when it
// has received and acknowledged our death.
@ -403,7 +413,7 @@ void Process::WaitedFor() // process_family_lock taken
{
parent = NULL;
limbo = false;
if ( groupfirst || sessionfirst )
if ( group_first || session_first || init_first )
limbo = true;
if ( !limbo )
delete this;
@ -429,12 +439,12 @@ void Process::ResetAddressSpace()
void Process::GroupRemoveMember(Process* child) // process_family_lock taken
{
assert(child->group == this);
if ( child->groupprev )
child->groupprev->groupnext = child->groupnext;
if ( child->group_prev )
child->group_prev->group_next = child->group_next;
else
groupfirst = child->groupnext;
if ( child->groupnext )
child->groupnext->groupprev = child->groupprev;
group_first = child->group_next;
if ( child->group_next )
child->group_next->group_prev = child->group_prev;
child->group = NULL;
if ( IsLimboDone() )
delete this;
@ -443,47 +453,61 @@ void Process::GroupRemoveMember(Process* child) // process_family_lock taken
void Process::SessionRemoveMember(Process* child) // process_family_lock taken
{
assert(child->session == this);
if ( child->sessionprev )
child->sessionprev->sessionnext = child->sessionnext;
if ( child->session_prev )
child->session_prev->session_next = child->session_next;
else
sessionfirst = child->sessionnext;
if ( child->sessionnext )
child->sessionnext->sessionprev = child->sessionprev;
session_first = child->session_next;
if ( child->session_next )
child->session_next->session_prev = child->session_prev;
child->session = NULL;
if ( !sessionfirst )
if ( !session_first )
{
// Remove reference to tty when session is empty.
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
tty.Reset();
}
if ( IsLimboDone() )
delete this;
}
void Process::InitRemoveMember(Process* child) // process_family_lock taken
{
assert(child->init == this);
if ( child->init_prev )
child->init_prev->init_next = child->init_next;
else
init_first = child->init_next;
if ( child->init_next )
child->init_next->init_prev = child->init_prev;
child->init = NULL;
if ( IsLimboDone() )
delete this;
}
bool Process::IsLimboDone() // process_family_lock taken
{
return limbo && !groupfirst && !sessionfirst;
return limbo && !group_first && !session_first && !init_first;
}
// process_family_lock taken
void Process::NotifyChildExit(Process* child, bool zombify)
{
if ( child->prevsibling )
child->prevsibling->nextsibling = child->nextsibling;
if ( child->nextsibling )
child->nextsibling->prevsibling = child->prevsibling;
if ( firstchild == child )
firstchild = child->nextsibling;
if ( firstchild )
firstchild->prevsibling = NULL;
if ( child->prev_sibling )
child->prev_sibling->next_sibling = child->next_sibling;
if ( child->next_sibling )
child->next_sibling->prev_sibling = child->prev_sibling;
if ( first_child == child )
first_child = child->next_sibling;
if ( first_child )
first_child->prev_sibling = NULL;
if ( zombify )
{
if ( zombiechild )
zombiechild->prevsibling = child;
child->prevsibling = NULL;
child->nextsibling = zombiechild;
zombiechild = child;
if ( zombie_child )
zombie_child->prev_sibling = child;
child->prev_sibling = NULL;
child->next_sibling = zombie_child;
zombie_child = child;
}
// Notify this parent process about the child exiting if it's meant to
@ -491,8 +515,8 @@ void Process::NotifyChildExit(Process* child, bool zombify)
// when init is exiting, because OnLastThreadExit needs to be able to catch
// every child exiting.
DeliverSignal(SIGCHLD);
if ( zombify || (is_init_exiting && Scheduler::GetInitProcess() == this) )
kthread_cond_broadcast(&zombiecond);
if ( zombify || is_init_exiting )
kthread_cond_broadcast(&zombie_cond);
}
pid_t Process::Wait(pid_t thepid, int* status_ptr, int options)
@ -504,7 +528,7 @@ pid_t Process::Wait(pid_t thepid, int* status_ptr, int options)
ScopedLock lock(&process_family_lock);
// A process can only wait if it has children.
if ( !firstchild && !zombiechild )
if ( !first_child && !zombie_child )
return errno = ECHILD, -1;
// Processes can only wait for their own children to exit.
@ -513,11 +537,11 @@ pid_t Process::Wait(pid_t thepid, int* status_ptr, int options)
// TODO: This is a slow but multithread safe way to verify that the
// target process has the correct parent.
bool found = false;
for ( Process* p = firstchild; !found && p; p = p->nextsibling )
if ( p->pid == thepid && !p->nozombify )
for ( Process* p = first_child; !found && p; p = p->next_sibling )
if ( p->pid == thepid && !p->no_zombify )
found = true;
for ( Process* p = zombiechild; !found && p; p = p->nextsibling )
if ( p->pid == thepid && !p->nozombify )
for ( Process* p = zombie_child; !found && p; p = p->next_sibling )
if ( p->pid == thepid && !p->no_zombify )
found = true;
if ( !found )
return errno = ECHILD, -1;
@ -526,26 +550,26 @@ pid_t Process::Wait(pid_t thepid, int* status_ptr, int options)
Process* zombie = NULL;
while ( !zombie )
{
for ( zombie = zombiechild; zombie; zombie = zombie->nextsibling )
if ( (thepid == -1 || thepid == zombie->pid) && !zombie->nozombify )
for ( zombie = zombie_child; zombie; zombie = zombie->next_sibling )
if ( (thepid == -1 || thepid == zombie->pid) && !zombie->no_zombify )
break;
if ( zombie )
break;
if ( options & WNOHANG )
return 0;
if ( !kthread_cond_wait_signal(&zombiecond, &process_family_lock) )
if ( !kthread_cond_wait_signal(&zombie_cond, &process_family_lock) )
return errno = EINTR, -1;
}
// Remove from the list of zombies.
if ( zombie->prevsibling )
zombie->prevsibling->nextsibling = zombie->nextsibling;
if ( zombie->nextsibling )
zombie->nextsibling->prevsibling = zombie->prevsibling;
if ( zombiechild == zombie )
zombiechild = zombie->nextsibling;
if ( zombiechild )
zombiechild->prevsibling = NULL;
if ( zombie->prev_sibling )
zombie->prev_sibling->next_sibling = zombie->next_sibling;
if ( zombie->next_sibling )
zombie->next_sibling->prev_sibling = zombie->prev_sibling;
if ( zombie_child == zombie )
zombie_child = zombie->next_sibling;
if ( zombie_child )
zombie_child->prev_sibling = NULL;
thepid = zombie->pid;
@ -584,7 +608,7 @@ void Process::ExitThroughSignal(int signal)
void Process::ExitWithCode(int requested_exit_code)
{
ScopedLock lock(&threadlock);
ScopedLock lock(&thread_lock);
if ( exit_code == -1 )
exit_code = requested_exit_code;
@ -592,74 +616,74 @@ void Process::ExitWithCode(int requested_exit_code)
// of process termination. We simply can't stop the threads as they may
// be running in kernel mode doing dangerous stuff. This thread will be
// destroyed by SIGKILL once the system call returns.
for ( Thread* t = firstthread; t; t = t->nextsibling )
for ( Thread* t = first_thread; t; t = t->next_sibling )
t->DeliverSignal(SIGKILL);
}
Ref<MountTable> Process::GetMTable()
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(mtable);
return mtable;
}
Ref<DescriptorTable> Process::GetDTable()
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(dtable);
return dtable;
}
Ref<ProcessTable> Process::GetPTable()
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(ptable);
return ptable;
}
Ref<Descriptor> Process::GetTTY()
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
return tty;
}
Ref<Descriptor> Process::GetRoot()
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(root);
return root;
}
Ref<Descriptor> Process::GetCWD()
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(cwd);
return cwd;
}
void Process::SetTTY(Ref<Descriptor> newtty)
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
tty = newtty;
}
void Process::SetRoot(Ref<Descriptor> newroot)
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(newroot);
root = newroot;
}
void Process::SetCWD(Ref<Descriptor> newcwd)
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(newcwd);
cwd = newcwd;
}
Ref<Descriptor> Process::GetDescriptor(int fd)
{
ScopedLock lock(&ptrlock);
ScopedLock lock(&ptr_lock);
assert(dtable);
return dtable->Get(fd);
}
@ -704,7 +728,7 @@ Process* Process::Fork()
kthread_mutex_lock(&process_family_lock);
// Forbid the creation of new processes if init has exited.
if ( is_init_exiting )
if ( init->is_init_exiting )
{
kthread_mutex_unlock(&process_family_lock);
clone->AbortConstruction();
@ -721,25 +745,32 @@ Process* Process::Fork()
// Remember the relation to the child process.
clone->parent = this;
clone->nextsibling = firstchild;
clone->prevsibling = NULL;
if ( firstchild )
firstchild->prevsibling = clone;
firstchild = clone;
clone->next_sibling = first_child;
clone->prev_sibling = NULL;
if ( first_child )
first_child->prev_sibling = clone;
first_child = clone;
// Add the new process to the current process group.
clone->group = group;
clone->groupprev = NULL;
if ( (clone->groupnext = group->groupfirst) )
group->groupfirst->groupprev = clone;
group->groupfirst = clone;
clone->group_prev = NULL;
if ( (clone->group_next = group->group_first) )
group->group_first->group_prev = clone;
group->group_first = clone;
// Add the new process to the current session.
clone->session = session;
clone->sessionprev = NULL;
if ( (clone->sessionnext = session->sessionfirst) )
session->sessionfirst->sessionprev = clone;
session->sessionfirst = clone;
clone->session_prev = NULL;
if ( (clone->session_next = session->session_first) )
session->session_first->session_prev = clone;
session->session_first = clone;
// Add the new process to the current init.
clone->init = init;
clone->init_prev = NULL;
if ( (clone->init_next = init->init_first) )
init->init_first->init_prev = clone;
init->init_first = clone;
kthread_mutex_unlock(&process_family_lock);
@ -749,22 +780,22 @@ Process* Process::Fork()
clone->resource_limits[i] = resource_limits[i];
kthread_mutex_unlock(&resource_limits_lock);
kthread_mutex_lock(&nicelock);
kthread_mutex_lock(&nice_lock);
clone->nice = nice;
kthread_mutex_unlock(&nicelock);
kthread_mutex_unlock(&nice_lock);
kthread_mutex_lock(&ptrlock);
kthread_mutex_lock(&ptr_lock);
clone->root = root;
clone->cwd = cwd;
kthread_mutex_unlock(&ptrlock);
kthread_mutex_unlock(&ptr_lock);
kthread_mutex_lock(&idlock);
kthread_mutex_lock(&id_lock);
clone->uid = uid;
clone->gid = gid;
clone->euid = euid;
clone->egid = egid;
clone->umask = umask;
kthread_mutex_unlock(&idlock);
kthread_mutex_unlock(&id_lock);
kthread_mutex_lock(&signal_lock);
memcpy(&clone->signal_actions, &signal_actions, sizeof(signal_actions));
@ -775,13 +806,13 @@ Process* Process::Fork()
// Initialize things that can fail and abort if needed.
bool failure = false;
kthread_mutex_lock(&ptrlock);
kthread_mutex_lock(&ptr_lock);
if ( !(clone->dtable = dtable->Fork()) )
failure = true;
//if ( !(clone->mtable = mtable->Fork()) )
// failure = true;
clone->mtable = mtable;
kthread_mutex_unlock(&ptrlock);
kthread_mutex_unlock(&ptr_lock);
if ( !(clone->program_image_path = String::Clone(program_image_path)) )
failure = true;
@ -1475,26 +1506,24 @@ pid_t sys_tfork(int flags, struct tfork* user_regs)
// TODO: Is it a hack to create a new kernel stack here?
Thread* curthread = CurrentThread();
size_t newkernelstacksize = curthread->kernelstacksize;
uint8_t* newkernelstack = new uint8_t[newkernelstacksize + stack_alignment];
size_t newkernel_stack_size = curthread->kernel_stack_size;
uint8_t* newkernelstack = new uint8_t[newkernel_stack_size + stack_alignment];
if ( !newkernelstack )
return -1;
uintptr_t stack_aligned = (uintptr_t) newkernelstack;
size_t stack_aligned_size = newkernelstacksize;
size_t stack_aligned_size = newkernel_stack_size;
if ( ((uintptr_t) stack_aligned) & (stack_alignment-1) )
stack_aligned = (stack_aligned + 16) & ~(stack_alignment-1);
stack_aligned_size &= 0xFFFFFFF0;
Process* parent_process = CurrentProcess();
Process* child_process;
if ( making_thread )
child_process = CurrentProcess();
else if ( !(child_process = CurrentProcess()->Fork()) )
{
delete[] newkernelstack;
return -1;
}
child_process = parent_process;
else if ( !(child_process = parent_process->Fork()) )
return delete[] newkernelstack, -1;
struct thread_registers cpuregs;
memset(&cpuregs, 0, sizeof(cpuregs));
@ -1550,7 +1579,7 @@ pid_t sys_tfork(int flags, struct tfork* user_regs)
// Forbid the creation of new threads if init has exited.
ScopedLock process_family_lock_lock(&process_family_lock);
if ( is_init_exiting )
if ( child_process->init->is_init_exiting )
return errno = EPERM, -1;
// If the thread could not be created, make the process commit suicide
@ -1565,9 +1594,9 @@ pid_t sys_tfork(int flags, struct tfork* user_regs)
return -1;
}
thread->kernelstackpos = (addr_t) newkernelstack;
thread->kernelstacksize = newkernelstacksize;
thread->kernelstackmalloced = true;
thread->kernel_stack_pos = (addr_t) newkernelstack;
thread->kernel_stack_size = newkernel_stack_size;
thread->kernel_stack_malloced = true;
memcpy(&thread->signal_mask, &regs.sigmask, sizeof(sigset_t));
memcpy(&thread->signal_stack, &regs.altstack, sizeof(stack_t));
@ -1604,7 +1633,8 @@ pid_t sys_getpgid(pid_t pid)
pid_t sys_getsid(pid_t pid)
{
ScopedLock lock(&process_family_lock);
Process* process = !pid ? CurrentProcess() : CurrentProcess()->GetPTable()->Get(pid);
Process* process =
!pid ? CurrentProcess() : CurrentProcess()->GetPTable()->Get(pid);
if ( !process )
return errno = ESRCH, -1;
if ( !process->session )
@ -1612,6 +1642,16 @@ pid_t sys_getsid(pid_t pid)
return process->session->pid;
}
pid_t sys_getinit(pid_t pid)
{
ScopedLock lock(&process_family_lock);
Process* process =
!pid ? CurrentProcess() : CurrentProcess()->GetPTable()->Get(pid);
if ( !process->init )
return errno = ESRCH, -1;
return process->init->pid;
}
int sys_setpgid(pid_t pid, pid_t pgid)
{
if ( pid < 0 || pgid < 0 )
@ -1649,13 +1689,13 @@ int sys_setpgid(pid_t pid, pid_t pgid)
return errno = EPERM, -1;
// The process must not be a process group leader.
// TODO: Maybe POSIX actually allows this.
if ( process->groupfirst )
if ( process->group_first )
return errno = EPERM, -1;
// The process must not be a session leader.
if ( process->sessionfirst )
if ( process->session_first )
return errno = EPERM, -1;
// The group must either exist or be the process itself.
if ( !group->groupfirst && group != process )
if ( !group->group_first && group != process )
return errno = EPERM, -1;
// Exit early if this is a noop.
@ -1667,11 +1707,11 @@ int sys_setpgid(pid_t pid, pid_t pgid)
process->group->GroupRemoveMember(process);
// Insert the process into its new process group.
process->groupprev = NULL;
process->groupnext = group->groupfirst;
if ( group->groupfirst )
group->groupfirst->groupprev = process;
group->groupfirst = process;
process->group_prev = NULL;
process->group_next = group->group_first;
if ( group->group_first )
group->group_first->group_prev = process;
group->group_first = process;
process->group = group;
return 0;
@ -1696,15 +1736,58 @@ pid_t sys_setsid(void)
process->session->SessionRemoveMember(process);
// Insert the process into its new session.
process->sessionprev = NULL;
process->sessionnext = NULL;
process->sessionfirst = process;
process->session_prev = NULL;
process->session_next = NULL;
process->session_first = process;
process->session = process;
// Insert the process into its new process group.
process->groupprev = NULL;
process->groupnext = NULL;
process->groupfirst = process;
process->group_prev = NULL;
process->group_next = NULL;
process->group_first = process;
process->group = process;
return process->pid;
}
int sys_setinit(void)
{
Process* process = CurrentProcess();
ScopedLock lock(&process_family_lock);
// Test if already a process group leader.
if ( process->group == process )
return errno = EPERM, -1;
// Remove the process from its current process group.
if ( process->group )
process->group->GroupRemoveMember(process);
// Remove the process from its current session.
if ( process->session )
process->session->SessionRemoveMember(process);
// Remove the process from its current init.
if ( process->init )
process->init->InitRemoveMember(process);
// Insert the process into its new init.
process->init_prev = NULL;
process->init_next = NULL;
process->init_first = process;
process->init = process;
// Insert the process into its new session.
process->session_prev = NULL;
process->session_next = NULL;
process->session_first = process;
process->session = process;
// Insert the process into its new process group.
process->group_prev = NULL;
process->group_next = NULL;
process->group_first = process;
process->group = process;
return process->pid;
@ -1718,7 +1801,7 @@ size_t sys_getpagesize(void)
mode_t sys_umask(mode_t newmask)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
mode_t oldmask = process->umask;
process->umask = newmask & 0666;
return oldmask;
@ -1727,7 +1810,7 @@ mode_t sys_umask(mode_t newmask)
mode_t sys_getumask(void)
{
Process* process = CurrentProcess();
ScopedLock lock(&process->idlock);
ScopedLock lock(&process->id_lock);
return process->umask;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2016, 2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2015, 2016, 2022, 2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -72,8 +72,8 @@ int sys_psctl(pid_t pid, int request, void* ptr)
{
Process* parent = process->parent;
psst.ppid = parent->pid;
psst.ppid_prev = process->prevsibling ? process->prevsibling->pid : -1;
psst.ppid_next = process->nextsibling ? process->nextsibling->pid : -1;
psst.ppid_prev = process->prev_sibling ? process->prev_sibling->pid : -1;
psst.ppid_next = process->next_sibling ? process->next_sibling->pid : -1;
}
else
{
@ -81,13 +81,13 @@ int sys_psctl(pid_t pid, int request, void* ptr)
psst.ppid_prev = -1;
psst.ppid_next = -1;
}
psst.ppid_first = process->firstchild ? process->firstchild->pid : -1;
psst.ppid_first = process->first_child ? process->first_child->pid : -1;
if ( process->group )
{
Process* group = process->group;
psst.pgid = group->pid;
psst.pgid_prev = process->groupprev ? process->groupprev->pid : -1;
psst.pgid_next = process->groupnext ? process->groupnext->pid : -1;
psst.pgid_prev = process->group_prev ? process->group_prev->pid : -1;
psst.pgid_next = process->group_next ? process->group_next->pid : -1;
}
else
{
@ -95,13 +95,13 @@ int sys_psctl(pid_t pid, int request, void* ptr)
psst.pgid_prev = -1;
psst.pgid_next = -1;
}
psst.pgid_first = process->groupfirst ? process->groupfirst->pid : -1;
psst.pgid_first = process->group_first ? process->group_first->pid : -1;
if ( process->session )
{
Process* session = process->session;
psst.sid = session->pid;
psst.sid_prev = process->sessionprev ? process->sessionprev->pid : -1;
psst.sid_next = process->sessionnext ? process->sessionnext->pid : -1;
psst.sid_prev = process->session_prev ? process->session_prev->pid : -1;
psst.sid_next = process->session_next ? process->session_next->pid : -1;
}
else
{
@ -109,24 +109,34 @@ int sys_psctl(pid_t pid, int request, void* ptr)
psst.sid_prev = -1;
psst.sid_next = -1;
}
psst.sid_first = process->sessionfirst ? process->sessionfirst->pid : -1;
// TODO: Implement init groupings.
psst.init = 1;
psst.init_prev = ptable->Prev(pid);
psst.init_next = ptable->Next(pid);
psst.init_first = pid == 1 ? 1 : -1;
kthread_mutex_lock(&process->idlock);
psst.sid_first = process->session_first ? process->session_first->pid : -1;
if ( process->init )
{
Process* init = process->init;
psst.init = init->pid;
psst.init_prev = process->init_prev ? process->init_prev->pid : -1;
psst.init_next = process->init_next ? process->init_next->pid : -1;
}
else
{
psst.init = -1;
psst.init_prev = -1;
psst.init_next = -1;
}
psst.init_first = process->init_first ? process->init_first->pid : -1;
kthread_mutex_lock(&process->id_lock);
psst.uid = process->uid;
psst.euid = process->euid;
psst.gid = process->gid;
psst.egid = process->egid;
kthread_mutex_unlock(&process->idlock);
kthread_mutex_lock(&process->threadlock);
kthread_mutex_unlock(&process->id_lock);
kthread_mutex_lock(&process->thread_lock);
psst.status = process->exit_code;
kthread_mutex_unlock(&process->threadlock);
kthread_mutex_lock(&process->nicelock);
kthread_mutex_unlock(&process->thread_lock);
kthread_mutex_lock(&process->nice_lock);
psst.nice = process->nice;
kthread_mutex_unlock(&process->nicelock);
kthread_mutex_unlock(&process->nice_lock);
kthread_mutex_lock(&process->segment_lock);
// TODO: Cache these.
for ( size_t i = 0; i < process->segments_used; i++ )

View file

@ -477,6 +477,8 @@ private:
size_t output_offset;
size_t output_used;
static const size_t output_size = 4096;
// TODO: This is not safe because ^W can produce unbounded output.
static const size_t output_probably_safe = 2048;
uint8_t output[output_size];
int ptynum;
@ -538,6 +540,16 @@ ssize_t PTY::master_write(ioctx_t* ctx, const uint8_t* buf, size_t count)
ScopedLockSignal lock(&termlock);
if ( !lock.IsAcquired() )
return errno = EINTR, -1;
// TODO: Work around deadlock by refusing writes when the buffer is starting
// to be too full. This can block / "deadlock" too if the caller
// didn't try to read the data written by a previous call.
while ( output_probably_safe <= output_used )
{
if ( ctx->dflags & O_NONBLOCK )
return errno = EWOULDBLOCK, -1;
if ( !kthread_cond_wait_signal(&output_possible_cond, &termlock) )
return errno = EINTR, -1;
}
size_t sofar = 0;
while ( sofar < count )
{
@ -550,9 +562,13 @@ ssize_t PTY::master_write(ioctx_t* ctx, const uint8_t* buf, size_t count)
{
if ( Signal::IsPending() )
return sofar ? (ssize_t) sofar : (errno = EINTR, -1);
// TODO: Unfortunately sequences like ^W can cause an unbounded
// number of tty_output data causing a deadlock.
ProcessByte(input[i]);
sofar++;
if ( output_probably_safe <= output_used )
return (ssize_t) sofar;
}
sofar += amount;
}
return (ssize_t) sofar;
}
@ -599,7 +615,7 @@ short PTY::PollMasterEventStatus()
short status = 0;
if ( output_used )
status |= POLLIN | POLLRDNORM;
if ( true /* can always write */ )
if ( output_used < output_probably_safe )
status |= POLLOUT | POLLWRNORM;
return status;
}
@ -656,6 +672,7 @@ int PTY::master_ioctl(ioctx_t* ctx, int cmd, uintptr_t arg)
const struct winsize* user_ws = (const struct winsize*) arg;
if ( !ctx->copy_from_src(&ws, user_ws, sizeof(ws)) )
return -1;
winch();
return 0;
}
return ioctl(ctx, cmd, arg);

View file

@ -39,7 +39,7 @@ static int GetProcessPriority(pid_t who)
Process* process = who ? CurrentProcess()->GetPTable()->Get(who) : CurrentProcess();
if ( !process )
return errno = ESRCH, -1;
ScopedLock lock(&process->nicelock);
ScopedLock lock(&process->nice_lock);
return process->nice;
}
@ -50,7 +50,7 @@ static int SetProcessPriority(pid_t who, int prio)
Process* process = who ? CurrentProcess()->GetPTable()->Get(who) : CurrentProcess();
if ( !process )
return errno = ESRCH, -1;
ScopedLock lock(&process->nicelock);
ScopedLock lock(&process->nice_lock);
process->nice = prio;
return 0;
}
@ -68,9 +68,9 @@ static int GetProcessGroupPriority(pid_t who)
if ( !group )
return errno = ESRCH, -1;
int lowest = INT_MAX;
for ( Process* process = group->groupfirst; process; process = process->groupnext )
for ( Process* process = group->group_first; process; process = process->group_next )
{
ScopedLock lock(&process->nicelock);
ScopedLock lock(&process->nice_lock);
if ( process->nice < lowest )
lowest = process->nice;
}
@ -84,9 +84,9 @@ static int SetProcessGroupPriority(pid_t who, int prio)
Process* group = who ? CurrentProcess()->GetPTable()->Get(who) : CurrentProcessGroup();
if ( !group )
return errno = ESRCH, -1;
for ( Process* process = group->groupfirst; process; process = process->groupnext )
for ( Process* process = group->group_first; process; process = process->group_next )
{
ScopedLock lock(&process->nicelock);
ScopedLock lock(&process->nice_lock);
process->nice = prio;
}
return 0;

View file

@ -181,8 +181,8 @@ extern "C" void fake_interrupt(void);
static void FakeInterruptedContext(struct interrupt_context* intctx, int int_no)
{
#if defined(__i386__)
uintptr_t stack = current_thread->kernelstackpos +
current_thread->kernelstacksize;
uintptr_t stack = current_thread->kernel_stack_pos +
current_thread->kernel_stack_size;
stack -= sizeof(struct interrupt_context);
struct interrupt_context* fakectx = (struct interrupt_context*) stack;
memcpy(fakectx, intctx, sizeof(struct interrupt_context));
@ -209,8 +209,8 @@ static void FakeInterruptedContext(struct interrupt_context* intctx, int int_no)
intctx->esp = stack;
intctx->ss = KDS | KRPL;
#elif defined(__x86_64__)
uintptr_t stack = current_thread->kernelstackpos +
current_thread->kernelstacksize;
uintptr_t stack = current_thread->kernel_stack_pos +
current_thread->kernel_stack_size;
stack -= sizeof(struct interrupt_context);
struct interrupt_context* fakectx = (struct interrupt_context*) stack;
memcpy(fakectx, intctx, sizeof(struct interrupt_context));
@ -268,7 +268,6 @@ static void SwitchRegisters(struct interrupt_context* intctx,
static Thread* idle_thread;
static Thread* first_runnable_thread;
static Thread* true_current_thread;
static Process* init_process;
static void SwitchThread(struct interrupt_context* intctx,
Thread* old_thread,
@ -407,16 +406,6 @@ void SetIdleThread(Thread* thread)
true_current_thread = thread;
}
void SetInitProcess(Process* init)
{
init_process = init;
}
Process* GetInitProcess()
{
return init_process;
}
Process* GetKernelProcess()
{
if ( !idle_thread )

View file

@ -144,8 +144,8 @@ int sys_sigaction(int signum,
memcpy(kact, &newact, sizeof(struct sigaction));
// Signals may become discarded because of the new handler.
ScopedLock threads_lock(&process->threadlock);
for ( Thread* t = process->firstthread; t; t = t->nextsibling )
ScopedLock threads_lock(&process->thread_lock);
for ( Thread* t = process->first_thread; t; t = t->next_sibling )
UpdatePendingSignals(t);
}
@ -315,9 +315,9 @@ int sys_kill(pid_t pid, int signum)
bool Process::DeliverGroupSignal(int signum) // process_family_lock held
{
if ( !groupfirst )
if ( !group_first )
return errno = ESRCH, false;
for ( Process* iter = groupfirst; iter; iter = iter->groupnext )
for ( Process* iter = group_first; iter; iter = iter->group_next )
{
int saved_errno = errno;
if ( !iter->DeliverSignal(signum) && errno != ESIGPENDING )
@ -331,9 +331,9 @@ bool Process::DeliverGroupSignal(int signum) // process_family_lock held
bool Process::DeliverSessionSignal(int signum) // process_family_lock held
{
if ( !sessionfirst )
if ( !session_first )
return errno = ESRCH, false;
for ( Process* iter = sessionfirst; iter; iter = iter->sessionnext )
for ( Process* iter = session_first; iter; iter = iter->session_next )
{
int saved_errno = errno;
if ( !iter->DeliverSignal(signum) && errno != ESIGPENDING )
@ -347,16 +347,16 @@ bool Process::DeliverSessionSignal(int signum) // process_family_lock held
bool Process::DeliverSignal(int signum)
{
ScopedLock lock(&threadlock);
ScopedLock lock(&thread_lock);
if ( !firstthread )
if ( !first_thread )
return errno = EINIT, false;
// Broadcast particular signals to all the threads in the process.
if ( signum == SIGCONT || signum == SIGSTOP || signum == SIGKILL )
{
int saved_errno = errno;
for ( Thread* t = firstthread; t; t = t->nextsibling )
for ( Thread* t = first_thread; t; t = t->next_sibling )
{
if ( !t->DeliverSignal(signum) && errno != ESIGPENDING )
{
@ -371,7 +371,7 @@ bool Process::DeliverSignal(int signum)
// TODO: This isn't how signals should be routed to a particular thread.
if ( CurrentThread()->process == this )
return CurrentThread()->DeliverSignal(signum);
return firstthread->DeliverSignal(signum);
return first_thread->DeliverSignal(signum);
}
int sys_raise(int signum)

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2016, 2021-2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2016, 2021-2024 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@ -204,6 +204,8 @@ void* syscall_list[SYSCALL_MAX_NUM + 1] =
[SYSCALL_SETDNSCONFIG] = (void*) sys_setdnsconfig,
[SYSCALL_FUTEX] = (void*) sys_futex,
[SYSCALL_MEMUSAGE] = (void*) sys_memusage,
[SYSCALL_GETINIT] = (void*) sys_getinit,
[SYSCALL_SETINIT] = (void*) sys_setinit,
[SYSCALL_MAX_NUM] = (void*) sys_bad_syscall,
};
} /* extern "C" */

View file

@ -59,18 +59,18 @@ Thread::Thread()
yield_to_tid = 0;
id = 0; // TODO: Make a thread id.
process = NULL;
prevsibling = NULL;
nextsibling = NULL;
prev_sibling = NULL;
next_sibling = NULL;
scheduler_list_prev = NULL;
scheduler_list_next = NULL;
state = NONE;
memset(&registers, 0, sizeof(registers));
kernelstackpos = 0;
kernelstacksize = 0;
kernel_stack_pos = 0;
kernel_stack_size = 0;
signal_count = 0;
signal_single_frame = 0;
signal_canary = 0;
kernelstackmalloced = false;
kernel_stack_malloced = false;
pledged_destruction = false;
force_no_signals = false;
signal_single = false;
@ -99,8 +99,8 @@ Thread::~Thread()
if ( process )
process->OnThreadDestruction(this);
assert(CurrentThread() != this);
if ( kernelstackmalloced )
delete[] (uint8_t*) kernelstackpos;
if ( kernel_stack_malloced )
delete[] (uint8_t*) kernel_stack_pos;
}
Thread* CreateKernelThread(Process* process,
@ -116,7 +116,7 @@ Thread* CreateKernelThread(Process* process,
return errno = EINVAL, (Thread*) NULL;
#endif
kthread_mutex_lock(&process->threadlock);
kthread_mutex_lock(&process->thread_lock);
// Note: Only allow the process itself to make threads, except the initial
// thread. This requirement is because kthread_exit() needs to know when
@ -124,7 +124,7 @@ Thread* CreateKernelThread(Process* process,
// and that no more threads will appear, so it can run some final process
// termination steps without any interference. It's always allowed to create
// threads in the kernel process as it never exits.
assert(!process->firstthread ||
assert(!process->first_thread ||
process == CurrentProcess() ||
process == Scheduler::GetKernelProcess());
@ -137,14 +137,14 @@ Thread* CreateKernelThread(Process* process,
// Create the family tree.
thread->process = process;
Thread* firsty = process->firstthread;
Thread* firsty = process->first_thread;
if ( firsty )
firsty->prevsibling = thread;
thread->nextsibling = firsty;
process->firstthread = thread;
firsty->prev_sibling = thread;
thread->next_sibling = firsty;
process->first_thread = thread;
process->threads_not_exiting_count++;
kthread_mutex_unlock(&process->threadlock);
kthread_mutex_unlock(&process->thread_lock);
return thread;
}
@ -260,9 +260,9 @@ Thread* CreateKernelThread(Process* process, void (*entry)(void*), void* user,
Thread* thread = CreateKernelThread(process, &regs, name);
if ( !thread ) { delete[] stack; return NULL; }
thread->kernelstackpos = (uintptr_t) stack;
thread->kernelstacksize = stacksize;
thread->kernelstackmalloced = true;
thread->kernel_stack_pos = (uintptr_t) stack;
thread->kernel_stack_size = stacksize;
thread->kernel_stack_malloced = true;
return thread;
}
@ -335,11 +335,11 @@ int sys_exit_thread(int requested_exit_code,
extended.unmap_size = Page::AlignUp(extended.unmap_size);
kthread_mutex_lock(&thread->process->threadlock);
kthread_mutex_lock(&thread->process->thread_lock);
bool is_others = false;
for ( Thread* iter = thread->process->firstthread;
for ( Thread* iter = thread->process->first_thread;
!is_others && iter;
iter = iter->nextsibling )
iter = iter->next_sibling )
{
if ( iter == thread )
continue;
@ -355,7 +355,7 @@ int sys_exit_thread(int requested_exit_code,
process->threads_exiting = true;
else if ( process->threads_exiting )
are_threads_exiting = true;
kthread_mutex_unlock(&thread->process->threadlock);
kthread_mutex_unlock(&thread->process->thread_lock);
// Self-destruct if another thread began exiting the process.
if ( are_threads_exiting )

Some files were not shown because too many files have changed in this diff Show more