Compare commits
30 Commits
6d08070639
...
68e1250d94
Author | SHA1 | Date |
---|---|---|
Jonas 'Sortie' Termansen | 68e1250d94 | |
Jonas 'Sortie' Termansen | 13ba297bb7 | |
Jonas 'Sortie' Termansen | be8d505007 | |
Jonas 'Sortie' Termansen | 321e52d541 | |
Jonas 'Sortie' Termansen | c5ab9c5da8 | |
Jonas 'Sortie' Termansen | de5314b2df | |
Jonas 'Sortie' Termansen | 74a0e9cf4c | |
Jonas 'Sortie' Termansen | f74581b948 | |
Jonas 'Sortie' Termansen | 80d9d548c5 | |
Jonas 'Sortie' Termansen | 19f1c87029 | |
Jonas 'Sortie' Termansen | bdc275794e | |
Jonas 'Sortie' Termansen | 780f85d0cb | |
Jonas 'Sortie' Termansen | 48699ef691 | |
Jonas 'Sortie' Termansen | 5cef36edf5 | |
Jonas 'Sortie' Termansen | 5f2f9c998d | |
Jonas 'Sortie' Termansen | 983fcf1a4f | |
Jonas 'Sortie' Termansen | 35b773e347 | |
Jonas 'Sortie' Termansen | 5e0e161fc0 | |
Jonas 'Sortie' Termansen | e07b827e98 | |
Jonas 'Sortie' Termansen | 606e67656a | |
Jonas 'Sortie' Termansen | ee9709babc | |
Jonas 'Sortie' Termansen | bd64649223 | |
Jonas 'Sortie' Termansen | ef2e937ee2 | |
Jonas 'Sortie' Termansen | 21d7317a57 | |
Jonas 'Sortie' Termansen | ba660cc66a | |
Jonas 'Sortie' Termansen | 57c52a97be | |
Jonas 'Sortie' Termansen | 7ebd4a4984 | |
Jonas 'Sortie' Termansen | 5d3819ea50 | |
Jonas 'Sortie' Termansen | 6c8e6e5fff | |
Jonas 'Sortie' Termansen | b384bce28c |
74
Makefile
74
Makefile
|
@ -6,8 +6,9 @@ include build-aux/version.mak
|
|||
MODULES=\
|
||||
libc \
|
||||
libm \
|
||||
dispd \
|
||||
libdisplay \
|
||||
libmount \
|
||||
libui \
|
||||
bench \
|
||||
carray \
|
||||
checksum \
|
||||
|
@ -15,23 +16,29 @@ chkblayout \
|
|||
chvideomode \
|
||||
dhclient \
|
||||
disked \
|
||||
display \
|
||||
dnsconfig \
|
||||
editor \
|
||||
ext \
|
||||
games \
|
||||
host \
|
||||
hostname \
|
||||
ifconfig \
|
||||
init \
|
||||
irc \
|
||||
iso9660 \
|
||||
kblayout \
|
||||
kblayout-compiler \
|
||||
login \
|
||||
mkinitrd \
|
||||
nyan \
|
||||
ping \
|
||||
regress \
|
||||
rw \
|
||||
sf \
|
||||
sh \
|
||||
sysinstall \
|
||||
terminal \
|
||||
tix \
|
||||
trianglix \
|
||||
update-initrd \
|
||||
|
@ -66,10 +73,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.initrd
|
||||
LIVE_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).live.initrd
|
||||
OVERLAY_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).overlay.initrd
|
||||
SRC_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).src.initrd
|
||||
|
@ -232,6 +242,8 @@ sysroot-system: sysroot-fsh sysroot-base-headers
|
|||
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 +281,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 +454,24 @@ 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
|
||||
mkinitrd --format=sortix-initrd-2 $(CHAIN_INITRD).d -o $(CHAIN_INITRD)
|
||||
rm -rf $(CHAIN_INITRD).d
|
||||
|
||||
$(LIVE_INITRD): sysroot
|
||||
mkdir -p `dirname $(LIVE_INITRD)`
|
||||
rm -rf $(LIVE_INITRD).d
|
||||
|
@ -452,6 +483,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/home
|
||||
mkdir -p $(LIVE_INITRD).d/root -m 700
|
||||
cp -RT "$(SYSROOT)/etc/skel" $(LIVE_INITRD).d/root
|
||||
|
@ -484,6 +518,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/home
|
||||
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
|
||||
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
|
||||
|
@ -524,6 +594,8 @@ else # none
|
|||
endif
|
||||
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
|
||||
|
||||
endif
|
||||
|
||||
.PHONY: iso
|
||||
iso: $(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
.PHONY: clean-tests
|
||||
clean-tests:
|
||||
rm -rf tests
|
||||
rm -f config.h
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-tests
|
||||
|
||||
config.h: $(addprefix tests/,$(addsuffix .h,$(TESTS)))
|
||||
cat tests/*.h > config.h
|
||||
|
||||
tests/%.h: ../build-aux/tests/%.c
|
||||
@if [ ! -d tests ]; then mkdir -p tests; fi
|
||||
@ln -sf ../../build-aux/tests/$*.c tests/$*.c
|
||||
@if $(CC) $(CFLAGS) $(CPPFLAGS) -Werror=incompatible-pointer-types -c tests/$*.c -o /dev/null 2>tests/$*.log; then \
|
||||
echo "# tests/$*: Yes" && tail -n 1 $< > $@; \
|
||||
else \
|
||||
echo "# tests/$*: No" && true > $@; \
|
||||
fi
|
||||
|
||||
-include ../build-aux/tests/*.d
|
|
@ -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,13 +113,23 @@ isinset() {
|
|||
cd "$directory"
|
||||
|
||||
kernel=$(maybe_compressed boot/sortix.bin)
|
||||
live_initrd=$(maybe_compressed boot/live.initrd)
|
||||
overlay_initrd=$(maybe_compressed boot/overlay.initrd)
|
||||
src_initrd=$(maybe_compressed boot/src.initrd)
|
||||
system_initrd=$(maybe_compressed boot/system.initrd)
|
||||
ports=$(ls repository |
|
||||
grep -E '\.tix\.tar\.xz$' |
|
||||
sed -E 's/\.tix\.tar\.xz$//')
|
||||
if $mount; then
|
||||
initrd=$(maybe_compressed boot/sortix.initrd)
|
||||
initrds=$initrd
|
||||
else
|
||||
live_initrd=$(maybe_compressed boot/live.initrd)
|
||||
overlay_initrd=$(maybe_compressed boot/overlay.initrd)
|
||||
src_initrd=$(maybe_compressed boot/src.initrd)
|
||||
system_initrd=$(maybe_compressed boot/system.initrd)
|
||||
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$//')
|
||||
fi
|
||||
|
||||
mkdir -p boot/grub
|
||||
mkdir -p boot/grub/init
|
||||
|
@ -183,6 +195,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'
|
||||
|
@ -198,11 +216,14 @@ fi
|
|||
set enable_src=true
|
||||
set enable_network_drivers=
|
||||
set enable_dhclient=true
|
||||
set enable_gui=true
|
||||
set enable_ntpd=false
|
||||
set enable_sshd=false
|
||||
|
||||
export version
|
||||
export machine
|
||||
export mount
|
||||
export chain
|
||||
export base_menu_title
|
||||
export menu_title
|
||||
export title_single_user
|
||||
|
@ -214,6 +235,7 @@ export no_random_seed
|
|||
export enable_src
|
||||
export enable_network_drivers
|
||||
export enable_dhclient
|
||||
export enable_gui
|
||||
export enable_ntpd
|
||||
export enable_sshd
|
||||
EOF
|
||||
|
@ -302,9 +324,10 @@ esac
|
|||
cat << EOF
|
||||
hook_kernel_pre
|
||||
echo -n "Loading /$kernel ($(human_size $kernel)) ... "
|
||||
multiboot /$kernel \$no_random_seed \$enable_network_drivers "\$@"
|
||||
multiboot /$kernel \$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
|
||||
|
@ -329,7 +352,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
|
||||
|
@ -402,10 +425,11 @@ menuentry() {
|
|||
echo
|
||||
printf "menuentry \"Sortix (%s)\" {\n" "$1"
|
||||
if [ -n "$2" ]; then
|
||||
printf " load_sortix %s\n" "$2"
|
||||
#printf " load_sortix '"
|
||||
#printf '%s' "$2" | sed "s,','\\'',g"
|
||||
#printf "'\n"
|
||||
printf " if \$enable_gui; then\n"
|
||||
printf " load_sortix %s-gui\n" "$2"
|
||||
printf " else\n"
|
||||
printf " load_sortix %s\n" "$2"
|
||||
printf " fi\n"
|
||||
else
|
||||
printf " load_sortix\n"
|
||||
fi
|
||||
|
@ -418,15 +442,17 @@ menu_title="\$base_menu_title"
|
|||
hook_menu_pre
|
||||
EOF
|
||||
|
||||
menuentry "\$title_single_user" '-- /sbin/init'
|
||||
menuentry "\$title_single_user" '-- /sbin/init --target=single-user'
|
||||
menuentry "\$title_sysinstall" '-- /sbin/init --target=sysinstall'
|
||||
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
|
||||
|
@ -446,6 +472,19 @@ menu_title="\$base_menu_title - Advanced Options"
|
|||
|
||||
hook_advanced_menu_pre
|
||||
|
||||
if "\$enable_gui"; then
|
||||
menuentry "Disable GUI" {
|
||||
enable_gui=false
|
||||
configfile /boot/grub/advanced.cfg
|
||||
}
|
||||
else
|
||||
menuentry "Enable GUI" {
|
||||
enable_gui=true
|
||||
configfile /boot/grub/advanced.cfg
|
||||
}
|
||||
fi
|
||||
|
||||
if ! $mount; then
|
||||
if "\$enable_src"; then
|
||||
menuentry "Disable loading source code" {
|
||||
enable_src=false
|
||||
|
@ -457,6 +496,7 @@ else
|
|||
configfile /boot/grub/advanced.cfg
|
||||
}
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "\$enable_network_drivers" = --disable-network-drivers ]; then
|
||||
menuentry "Enable networking drivers" {
|
||||
|
@ -470,6 +510,7 @@ else
|
|||
}
|
||||
fi
|
||||
|
||||
if ! $mount; then
|
||||
if \$enable_dhclient; then
|
||||
menuentry "Disable DHCP client" {
|
||||
enable_dhclient=false
|
||||
|
@ -509,6 +550,7 @@ fi
|
|||
menuentry "Select binary packages..." {
|
||||
configfile /boot/grub/tix.cfg
|
||||
}
|
||||
fi
|
||||
|
||||
hook_advanced_menu_post
|
||||
EOF
|
||||
|
|
|
@ -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 libcurl libcurses libssl libstdc++ nano ntpd make patch pkg-config python ssh tar vim wget xz xorriso"
|
||||
set_minimal="cut dash e2fsprogs grep grub libssl mdocml sed signify tar wget xargs xorriso xz"
|
||||
set_basic="$set_minimal binutils bison bzip2 diffutils ed flex gawk gcc git gzip libcurl libcurses libstdc++ make m4 nano ntpd patch perl pkg-config python ssh texinfo vim"
|
||||
sets="basic minimal"
|
||||
|
|
|
@ -151,6 +151,7 @@ EOF
|
|||
tix-iso-bootconfig \
|
||||
--random-seed \
|
||||
--timeout=0 \
|
||||
--disable-gui \
|
||||
--liveconfig=liveconfig \
|
||||
bootconfig
|
||||
mkdir -p bootconfig/boot/grub
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#include <string.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
void (*ptr)(void*, size_t) = explicit_bzero;
|
||||
return ptr ? 0 : 1;
|
||||
}
|
||||
#define HAVE_EXPLICIT_BZERO 1
|
|
@ -0,0 +1,8 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
void* (*ptr)(void*, size_t, size_t) = reallocarray;
|
||||
return ptr ? 0 : 1;
|
||||
}
|
||||
#define HAVE_REALLOCARRAY 1
|
|
@ -0,0 +1,8 @@
|
|||
#include <string.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
size_t (*ptr)(char*, const char*, size_t) = strlcat;
|
||||
return ptr ? 0 : 1;
|
||||
}
|
||||
#define HAVE_STRLCAT 1
|
|
@ -0,0 +1,8 @@
|
|||
#include <string.h>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
size_t (*ptr)(char*, const char*, size_t) = strlcpy;
|
||||
return ptr ? 0 : 1;
|
||||
}
|
||||
#define HAVE_STRLCPY 1
|
|
@ -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
|
||||
|
|
|
@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra
|
|||
BINARIES = chkblayout
|
||||
MANPAGES1 = chkblayout.1
|
||||
|
||||
LIBS =
|
||||
LIBS = -ldisplay
|
||||
|
||||
all: $(BINARIES)
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <display.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
|
@ -34,6 +35,20 @@
|
|||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
|
||||
#define CHKBLAYOUT_ID 0
|
||||
|
||||
static bool chkblayout_ack_received = false;
|
||||
static int chkblayout_error;
|
||||
|
||||
static void on_ack(void* ctx, uint32_t id, int32_t error)
|
||||
{
|
||||
(void) ctx;
|
||||
if ( id != CHKBLAYOUT_ID )
|
||||
return;
|
||||
chkblayout_error = error;
|
||||
chkblayout_ack_received = true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
bool list = false;
|
||||
|
@ -103,8 +118,29 @@ int main(int argc, char* argv[])
|
|||
err(1, "read: %s", kblayout_path);
|
||||
close(kblayout_fd);
|
||||
|
||||
if ( tcsetblob(tty_fd, "kblayout", kblayout, kblayout_size) < 0 )
|
||||
err(1, "tcsetblob: kblayout: %s:", kblayout_path);
|
||||
if ( getenv("DISPLAY_SOCKET") )
|
||||
{
|
||||
struct display_connection* connection = display_connect_default();
|
||||
if ( !connection )
|
||||
err(1, "Could not connect to display server");
|
||||
|
||||
display_chkblayout(connection, CHKBLAYOUT_ID, kblayout, kblayout_size);
|
||||
|
||||
struct display_event_handlers handlers = {0};
|
||||
handlers.ack_handler = on_ack;
|
||||
while ( !chkblayout_ack_received )
|
||||
display_wait_event(connection, &handlers);
|
||||
|
||||
if ( chkblayout_error )
|
||||
{
|
||||
errno = chkblayout_error;
|
||||
err(1, "tcsetblob: kblayout: %s", kblayout_path);
|
||||
}
|
||||
|
||||
display_disconnect(connection);
|
||||
}
|
||||
else if ( tcsetblob(tty_fd, "kblayout", kblayout, kblayout_size) < 0 )
|
||||
err(1, "tcsetblob: kblayout: %s", kblayout_path);
|
||||
|
||||
free(kblayout);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ CFLAGS += -Wall -Wextra
|
|||
BINARIES = chvideomode
|
||||
MANPAGES1 = chvideomode.1
|
||||
|
||||
LIBS =
|
||||
LIBS = -ldisplay
|
||||
|
||||
all: $(BINARIES)
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <display.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -37,7 +39,22 @@
|
|||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
struct termios saved;
|
||||
#define REQUEST_DISPLAYS_ID 0
|
||||
#define REQUEST_DISPLAY_MODES_ID 1
|
||||
#define SET_DISPLAY_MODE_ID 2
|
||||
|
||||
static uint32_t display_id;
|
||||
static bool displays_received = false;
|
||||
|
||||
static size_t modes_count = 0;
|
||||
static struct dispmsg_crtc_mode* modes;
|
||||
static int request_display_modes_error = 0;
|
||||
static bool modes_received = false;
|
||||
|
||||
static int set_display_mode_error = 0;
|
||||
static bool set_display_mode_ack_received;
|
||||
|
||||
static struct termios saved;
|
||||
|
||||
static void restore_terminal(int sig)
|
||||
{
|
||||
|
@ -48,6 +65,88 @@ static void restore_terminal(int sig)
|
|||
raise(sig);
|
||||
}
|
||||
|
||||
static void on_displays(void* ctx, uint32_t id, uint32_t displays)
|
||||
{
|
||||
(void) ctx;
|
||||
if ( id != REQUEST_DISPLAYS_ID )
|
||||
return;
|
||||
if ( displays < 1 )
|
||||
errx(1, "No displays available");
|
||||
display_id = 0; // TODO: Multimonitor support.
|
||||
displays_received = true;
|
||||
}
|
||||
|
||||
static void on_display_modes(void* ctx, uint32_t id,
|
||||
uint32_t display_modes_count,
|
||||
void* aux, size_t aux_size)
|
||||
{
|
||||
(void) ctx;
|
||||
assert(display_modes_count * sizeof(struct dispmsg_crtc_mode) == aux_size);
|
||||
if ( id != REQUEST_DISPLAY_MODES_ID )
|
||||
return;
|
||||
modes = malloc(aux_size);
|
||||
if ( !modes )
|
||||
err(1, "malloc");
|
||||
memcpy(modes, aux, aux_size);
|
||||
modes_count = display_modes_count;
|
||||
modes_received = true;
|
||||
}
|
||||
|
||||
static void on_ack(void* ctx, uint32_t id, int32_t error)
|
||||
{
|
||||
(void) ctx;
|
||||
switch ( id )
|
||||
{
|
||||
case REQUEST_DISPLAY_MODES_ID:
|
||||
if ( error )
|
||||
{
|
||||
modes = NULL;
|
||||
request_display_modes_error = error;
|
||||
modes_received = true;
|
||||
}
|
||||
break;
|
||||
case SET_DISPLAY_MODE_ID:
|
||||
set_display_mode_error = error;
|
||||
set_display_mode_ack_received = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void request_displays(struct display_connection* connection)
|
||||
{
|
||||
display_request_displays(connection, REQUEST_DISPLAYS_ID);
|
||||
struct display_event_handlers handlers = {0};
|
||||
handlers.displays_handler = on_displays;
|
||||
while ( !displays_received )
|
||||
display_wait_event(connection, &handlers);
|
||||
}
|
||||
|
||||
static void request_display_modes(struct display_connection* connection,
|
||||
uint32_t display_id)
|
||||
{
|
||||
display_request_display_modes(connection, REQUEST_DISPLAY_MODES_ID,
|
||||
display_id);
|
||||
struct display_event_handlers handlers = {0};
|
||||
handlers.display_modes_handler = on_display_modes;
|
||||
handlers.ack_handler = on_ack;
|
||||
while ( !modes_received )
|
||||
display_wait_event(connection, &handlers);
|
||||
errno = request_display_modes_error;
|
||||
}
|
||||
|
||||
static bool request_set_display_mode(struct display_connection* connection,
|
||||
uint32_t display_id,
|
||||
struct dispmsg_crtc_mode mode)
|
||||
{
|
||||
display_set_display_mode(connection, SET_DISPLAY_MODE_ID, display_id, mode);
|
||||
struct display_event_handlers handlers = {0};
|
||||
handlers.ack_handler = on_ack;
|
||||
set_display_mode_ack_received = false;
|
||||
while ( !set_display_mode_ack_received )
|
||||
display_wait_event(connection, &handlers);
|
||||
return !(errno = set_display_mode_error);
|
||||
}
|
||||
|
||||
static bool set_current_mode(const struct tiocgdisplay* display,
|
||||
struct dispmsg_crtc_mode mode)
|
||||
{
|
||||
|
@ -61,7 +160,7 @@ static bool set_current_mode(const struct tiocgdisplay* display,
|
|||
|
||||
static struct dispmsg_crtc_mode*
|
||||
get_available_modes(const struct tiocgdisplay* display,
|
||||
size_t* num_modes_ptr)
|
||||
size_t* modes_count_ptr)
|
||||
{
|
||||
struct dispmsg_get_crtc_modes msg;
|
||||
msg.msgid = DISPMSG_GET_CRTC_MODES;
|
||||
|
@ -70,15 +169,15 @@ get_available_modes(const struct tiocgdisplay* display,
|
|||
size_t guess = 1;
|
||||
while ( true )
|
||||
{
|
||||
struct dispmsg_crtc_mode* ret = (struct dispmsg_crtc_mode*)
|
||||
malloc(sizeof(struct dispmsg_crtc_mode) * guess);
|
||||
struct dispmsg_crtc_mode* ret =
|
||||
calloc(guess, sizeof(struct dispmsg_crtc_mode));
|
||||
if ( !ret )
|
||||
return NULL;
|
||||
msg.modes_length = guess;
|
||||
msg.modes = ret;
|
||||
if ( dispmsg_issue(&msg, sizeof(msg)) == 0 )
|
||||
{
|
||||
*num_modes_ptr = guess;
|
||||
*modes_count_ptr = guess;
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
|
@ -144,21 +243,21 @@ static bool mode_passes_filter(struct dispmsg_crtc_mode mode,
|
|||
}
|
||||
|
||||
static void filter_modes(struct dispmsg_crtc_mode* modes,
|
||||
size_t* num_modes_ptr,
|
||||
size_t* modes_count_ptr,
|
||||
struct filter* filter)
|
||||
{
|
||||
size_t in_num = *num_modes_ptr;
|
||||
size_t out_num = 0;
|
||||
for ( size_t i = 0; i < in_num; i++ )
|
||||
size_t in_count = *modes_count_ptr;
|
||||
size_t out_count = 0;
|
||||
for ( size_t i = 0; i < in_count; i++ )
|
||||
{
|
||||
if ( mode_passes_filter(modes[i], filter) )
|
||||
modes[out_num++] = modes[i];
|
||||
modes[out_count++] = modes[i];
|
||||
}
|
||||
*num_modes_ptr = out_num;
|
||||
*modes_count_ptr = out_count;
|
||||
}
|
||||
|
||||
static bool get_mode(struct dispmsg_crtc_mode* modes,
|
||||
size_t num_modes,
|
||||
size_t modes_count,
|
||||
unsigned int xres,
|
||||
unsigned int yres,
|
||||
unsigned int bpp,
|
||||
|
@ -168,7 +267,7 @@ static bool get_mode(struct dispmsg_crtc_mode* modes,
|
|||
bool found_other = false;
|
||||
size_t index;
|
||||
size_t other_index = 0;
|
||||
for ( size_t i = 0; i < num_modes; i++ )
|
||||
for ( size_t i = 0; i < modes_count; i++ )
|
||||
{
|
||||
if ( modes[i].view_xres == xres &&
|
||||
modes[i].view_yres == yres &&
|
||||
|
@ -208,16 +307,16 @@ static bool get_mode(struct dispmsg_crtc_mode* modes,
|
|||
}
|
||||
|
||||
static bool select_mode(struct dispmsg_crtc_mode* modes,
|
||||
size_t num_modes,
|
||||
size_t modes_count,
|
||||
int mode_set_error,
|
||||
struct dispmsg_crtc_mode* mode)
|
||||
{
|
||||
if ( !isatty(0) )
|
||||
errx(1, "Interactive menu requires stdin to be a terminal");
|
||||
|
||||
int num_modes_display_length = 1;
|
||||
for ( size_t i = num_modes; 10 <= i; i /= 10 )
|
||||
num_modes_display_length++;
|
||||
int modes_count_display_length = 1;
|
||||
for ( size_t i = modes_count; 10 <= i; i /= 10 )
|
||||
modes_count_display_length++;
|
||||
|
||||
size_t selection = 0;
|
||||
bool decided = false;
|
||||
|
@ -239,7 +338,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes,
|
|||
size_t entries_per_page = ws.ws_row - off;
|
||||
size_t page = selection / entries_per_page;
|
||||
size_t from = page * entries_per_page;
|
||||
size_t how_many_available = num_modes - from;
|
||||
size_t how_many_available = modes_count - from;
|
||||
size_t how_many = entries_per_page;
|
||||
if ( how_many_available < how_many )
|
||||
how_many = how_many_available;
|
||||
|
@ -259,7 +358,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes,
|
|||
const char* color = index == selection ? "\e[31m" : "\e[m";
|
||||
printf("%s", color);
|
||||
printf("\e[2K");
|
||||
printf(" [%-*zu] ", num_modes_display_length, index);
|
||||
printf(" [%-*zu] ", modes_count_display_length, index);
|
||||
if ( modes[index].control & DISPMSG_CONTROL_VALID )
|
||||
printf("%ux%ux%u",
|
||||
modes[index].view_xres,
|
||||
|
@ -325,12 +424,12 @@ static bool select_mode(struct dispmsg_crtc_mode* modes,
|
|||
if ( selection )
|
||||
selection--;
|
||||
else
|
||||
selection = num_modes - 1;
|
||||
selection = modes_count - 1;
|
||||
redraw = true;
|
||||
}
|
||||
else if ( length == 1 && byte == 'B' ) // Down key
|
||||
{
|
||||
if ( selection + 1 == num_modes )
|
||||
if ( selection + 1 == modes_count )
|
||||
selection = 0;
|
||||
else
|
||||
selection++;
|
||||
|
@ -343,7 +442,7 @@ static bool select_mode(struct dispmsg_crtc_mode* modes,
|
|||
else if ( '0' <= byte && byte <= '9' )
|
||||
{
|
||||
uint32_t requested = byte - '0';
|
||||
if ( requested < num_modes )
|
||||
if ( requested < modes_count )
|
||||
{
|
||||
selection = requested;
|
||||
redraw = true;
|
||||
|
@ -510,22 +609,37 @@ int main(int argc, char* argv[])
|
|||
}
|
||||
}
|
||||
|
||||
bool use_display = getenv("DISPLAY_SOCKET");
|
||||
|
||||
struct display_connection* connection;
|
||||
struct tiocgdisplay display;
|
||||
struct tiocgdisplays gdisplays = {0};
|
||||
gdisplays.count = 1;
|
||||
gdisplays.displays = &display;
|
||||
if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 )
|
||||
if ( use_display )
|
||||
{
|
||||
fprintf(stderr, "No video devices are associated with this terminal.\n");
|
||||
exit(13);
|
||||
connection = display_connect_default();
|
||||
if ( !connection )
|
||||
err(1, "Could not connect to display server");
|
||||
request_displays(connection);
|
||||
request_display_modes(connection, display_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
struct tiocgdisplays gdisplays = {0};
|
||||
// TODO: Multimonitor support.
|
||||
gdisplays.count = 1;
|
||||
gdisplays.displays = &display;
|
||||
if ( ioctl(1, TIOCGDISPLAYS, &gdisplays) < 0 || gdisplays.count == 0 )
|
||||
{
|
||||
fprintf(stderr, "No displays associated with this terminal.\n");
|
||||
exit(13);
|
||||
}
|
||||
|
||||
modes = get_available_modes(&display, &modes_count);
|
||||
}
|
||||
|
||||
size_t num_modes = 0;
|
||||
struct dispmsg_crtc_mode* modes = get_available_modes(&display, &num_modes);
|
||||
if ( !modes )
|
||||
err(1, "Unable to detect available video modes");
|
||||
|
||||
if ( !num_modes )
|
||||
if ( !modes_count )
|
||||
{
|
||||
fprintf(stderr, "No video modes are currently available.\n");
|
||||
fprintf(stderr, "Try make sure a device driver exists and is "
|
||||
|
@ -533,8 +647,8 @@ int main(int argc, char* argv[])
|
|||
exit(11);
|
||||
}
|
||||
|
||||
filter_modes(modes, &num_modes, &filter);
|
||||
if ( !num_modes )
|
||||
filter_modes(modes, &modes_count, &filter);
|
||||
if ( !modes_count )
|
||||
{
|
||||
fprintf(stderr, "No video mode remains after filtering away unwanted "
|
||||
"modes.\n");
|
||||
|
@ -552,10 +666,15 @@ int main(int argc, char* argv[])
|
|||
errx(1, "Invalid video mode: %s", argv[optind]);
|
||||
|
||||
struct dispmsg_crtc_mode mode;
|
||||
if ( !get_mode(modes, num_modes, xres, yres, bpp, &mode) )
|
||||
if ( !get_mode(modes, modes_count, xres, yres, bpp, &mode) )
|
||||
errx(1, "No such available resolution: %s", argv[optind]);
|
||||
|
||||
if ( !set_current_mode(&display, mode) )
|
||||
bool mode_set;
|
||||
if ( use_display )
|
||||
mode_set = request_set_display_mode(connection, display_id, mode);
|
||||
else
|
||||
mode_set = set_current_mode(&display, mode);
|
||||
if ( !mode_set )
|
||||
err(1, "Failed to set video mode %jux%jux%ju",
|
||||
(uintmax_t) mode.view_xres,
|
||||
(uintmax_t) mode.view_yres,
|
||||
|
@ -568,10 +687,15 @@ int main(int argc, char* argv[])
|
|||
while ( !mode_set )
|
||||
{
|
||||
struct dispmsg_crtc_mode mode;
|
||||
if ( !select_mode(modes, num_modes, mode_set_error, &mode) )
|
||||
if ( !select_mode(modes, modes_count, mode_set_error, &mode) )
|
||||
exit(10);
|
||||
|
||||
if ( !(mode_set = set_current_mode(&display, mode)) )
|
||||
if ( use_display )
|
||||
mode_set = request_set_display_mode(connection, display_id,
|
||||
mode);
|
||||
else
|
||||
mode_set = set_current_mode(&display, mode);
|
||||
if ( !mode_set )
|
||||
{
|
||||
mode_set_error = errno;
|
||||
warn("Failed to set video mode %jux%jux%ju",
|
||||
|
@ -582,5 +706,8 @@ int main(int argc, char* argv[])
|
|||
}
|
||||
}
|
||||
|
||||
if ( use_display )
|
||||
display_disconnect(connection);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
*.a
|
||||
*.o
|
||||
server/dispd
|
|
@ -1,53 +0,0 @@
|
|||
SOFTWARE_MEANT_FOR_SORTIX=1
|
||||
include ../build-aux/platform.mak
|
||||
include ../build-aux/compiler.mak
|
||||
include ../build-aux/version.mak
|
||||
include ../build-aux/dirs.mak
|
||||
|
||||
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
|
||||
CFLAGS?=$(OPTLEVEL)
|
||||
|
||||
CFLAGS:=$(CFLAGS) -Wall -Wextra
|
||||
CPPFLAGS:=$(CPPFLAGS) -I include
|
||||
|
||||
CLIENT_OBJS:=\
|
||||
client/library.o \
|
||||
client/session.o \
|
||||
client/window.o \
|
||||
|
||||
BINS:=client/libdispd.a
|
||||
|
||||
all: $(BINS)
|
||||
|
||||
.PHONY: all headers client clean install install-include-dirs \
|
||||
install-headers install-client-dirs install-client
|
||||
|
||||
headers:
|
||||
|
||||
client: client/libdispd.a
|
||||
|
||||
client/libdispd.a: $(CLIENT_OBJS)
|
||||
$(AR) rcs $@ $(CLIENT_OBJS)
|
||||
|
||||
clean:
|
||||
rm -f $(CLIENT_OBJS)
|
||||
rm -f $(BINS)
|
||||
rm -f *.o client/*.o
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
# Installation into sysroot
|
||||
install: install-headers install-client
|
||||
|
||||
install-include-dirs: headers
|
||||
mkdir -p $(DESTDIR)$(INCLUDEDIR)
|
||||
|
||||
install-headers: install-include-dirs headers
|
||||
cp -RTv include $(DESTDIR)$(INCLUDEDIR)
|
||||
|
||||
install-client-dirs:
|
||||
mkdir -p $(DESTDIR)$(LIBDIR)
|
||||
|
||||
install-client: install-client-dirs client
|
||||
cp -P client/libdispd.a $(DESTDIR)$(LIBDIR)
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, 2013, 2014, 2016 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.
|
||||
*
|
||||
* session.c
|
||||
* Handles session management.
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/display.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dispd.h>
|
||||
|
||||
#include "session.h"
|
||||
|
||||
struct dispd_session* global_session = NULL;
|
||||
|
||||
bool dispd__session_initialize(int* argc, char*** argv)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
size_t size = sizeof(struct dispd_session);
|
||||
global_session = (struct dispd_session*) malloc(size);
|
||||
if ( !global_session )
|
||||
return false;
|
||||
memset(global_session, 0, sizeof(*global_session));
|
||||
int tty_fd = open("/dev/tty", O_RDWR);
|
||||
if ( tty_fd < 0 )
|
||||
return free(global_session), false;
|
||||
struct tiocgdisplay display;
|
||||
struct tiocgdisplays gdisplays;
|
||||
memset(&gdisplays, 0, sizeof(gdisplays));
|
||||
gdisplays.count = 1;
|
||||
gdisplays.displays = &display;
|
||||
bool fail = ioctl(tty_fd, TIOCGDISPLAYS, &gdisplays) < 0 ||
|
||||
gdisplays.count == 0;
|
||||
close(tty_fd);
|
||||
if ( fail )
|
||||
return free(global_session), false;
|
||||
global_session->device = display.device;
|
||||
global_session->connector = display.connector;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct dispd_session* dispd_attach_default_session()
|
||||
{
|
||||
global_session->refcount++;
|
||||
return global_session;
|
||||
}
|
||||
|
||||
bool dispd_detach_session(struct dispd_session* session)
|
||||
{
|
||||
session->refcount--;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dispd_session_setup_game_rgba(struct dispd_session* session)
|
||||
{
|
||||
if ( session->is_rgba )
|
||||
return true;
|
||||
if ( session->current_window )
|
||||
return false;
|
||||
struct dispmsg_get_crtc_mode msg;
|
||||
msg.msgid = DISPMSG_GET_CRTC_MODE;
|
||||
msg.device = session->device;
|
||||
msg.connector = session->connector;
|
||||
if ( dispmsg_issue(&msg, sizeof(msg)) != 0 )
|
||||
return false;
|
||||
if ( !(msg.mode.control & 1) || msg.mode.fb_format != 32 )
|
||||
{
|
||||
pid_t childpid = fork();
|
||||
if ( childpid < 0 )
|
||||
return false;
|
||||
if ( childpid )
|
||||
{
|
||||
int status;
|
||||
waitpid(childpid, &status, 0);
|
||||
return session->is_rgba = (WIFEXITED(status) && !WEXITSTATUS(status));
|
||||
}
|
||||
const char* chvideomode = "chvideomode";
|
||||
#if 1
|
||||
// TODO chvideomode currently launches --bpp 32 as a program...
|
||||
execlp(chvideomode, chvideomode, (const char*) NULL);
|
||||
#else
|
||||
execlp(chvideomode, chvideomode,
|
||||
"--bpp", "32",
|
||||
"--show-graphics", "true",
|
||||
"--show-text", "false",
|
||||
(const char*) NULL);
|
||||
#endif
|
||||
err(127, "%s", chvideomode);
|
||||
}
|
||||
|
||||
// HACK: The console may be rendered asynchronously and it might still be
|
||||
// rendering to the framebuffer, however this process is about to do
|
||||
// bitmapped graphics to the framebuffer as well. Since there is no
|
||||
// synchronization with the terminal except not writing to it, there
|
||||
// is a small window where both are fighting for the framebuffer.
|
||||
// We can resolve this issue by simply fsync()ing the terminal, which
|
||||
// causes the scheduled console rendering to finish before returning.
|
||||
int tty_fd = open("/dev/tty", O_WRONLY);
|
||||
if ( 0 <= tty_fd )
|
||||
{
|
||||
fsync(tty_fd);
|
||||
close(tty_fd);
|
||||
}
|
||||
|
||||
return session->is_rgba = true;
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, 2013, 2014, 2016 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.
|
||||
*
|
||||
* window.c
|
||||
* Handles windows.
|
||||
*/
|
||||
|
||||
#include <sys/display.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <ioleast.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dispd.h>
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "session.h"
|
||||
#include "window.h"
|
||||
|
||||
struct dispd_window* dispd_create_window_game_rgba(struct dispd_session* session)
|
||||
{
|
||||
if ( session->current_window )
|
||||
return NULL;
|
||||
if ( !session->is_rgba )
|
||||
return NULL;
|
||||
size_t size = sizeof(struct dispd_window);
|
||||
struct dispd_window* window = (struct dispd_window*) malloc(size);
|
||||
if ( !window )
|
||||
return NULL;
|
||||
memset(window, 0, sizeof(*window));
|
||||
window->session = session;
|
||||
return session->current_window = window;
|
||||
}
|
||||
|
||||
bool dispd_destroy_window(struct dispd_window* window)
|
||||
{
|
||||
free(window->cached_buffer);
|
||||
free(window);
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t* request_buffer(struct dispd_window* window, size_t size)
|
||||
{
|
||||
if ( window->cached_buffer )
|
||||
{
|
||||
if ( window->cached_buffer_size == size )
|
||||
{
|
||||
uint8_t* ret = window->cached_buffer;
|
||||
window->cached_buffer = NULL;
|
||||
window->cached_buffer_size = 0;
|
||||
return ret;
|
||||
}
|
||||
free(window->cached_buffer);
|
||||
window->cached_buffer = NULL;
|
||||
window->cached_buffer_size = 0;
|
||||
}
|
||||
return (uint8_t*) malloc(size);
|
||||
}
|
||||
|
||||
static void return_buffer(struct dispd_window* window, uint8_t* b, size_t size)
|
||||
{
|
||||
free(window->cached_buffer);
|
||||
window->cached_buffer = b;
|
||||
window->cached_buffer_size = size;
|
||||
}
|
||||
|
||||
struct dispd_framebuffer* dispd_begin_render(struct dispd_window* window)
|
||||
{
|
||||
struct dispmsg_get_crtc_mode msg;
|
||||
msg.msgid = DISPMSG_GET_CRTC_MODE;
|
||||
msg.device = window->session->device;
|
||||
msg.connector = window->session->connector;
|
||||
if ( dispmsg_issue(&msg, sizeof(msg)) != 0 )
|
||||
return NULL;
|
||||
size_t size = sizeof(struct dispd_framebuffer);
|
||||
struct dispd_framebuffer* fb = (struct dispd_framebuffer*) malloc(size);
|
||||
if ( !fb )
|
||||
return NULL;
|
||||
memset(fb, 0, sizeof(*fb));
|
||||
fb->window = window;
|
||||
fb->width = msg.mode.view_xres;
|
||||
fb->height = msg.mode.view_yres;
|
||||
fb->bpp = 32;
|
||||
fb->pitch = fb->width * fb->bpp / 8;
|
||||
fb->datasize = fb->pitch * fb->height;
|
||||
fb->data = (uint8_t*) request_buffer(window, fb->datasize);
|
||||
fb->fb_location = msg.mode.fb_location;
|
||||
if ( !fb->data ) { free(fb); return NULL; }
|
||||
return fb;
|
||||
}
|
||||
|
||||
bool dispd_finish_render(struct dispd_framebuffer* fb)
|
||||
{
|
||||
struct dispd_window* window = fb->window;
|
||||
bool ret = false;
|
||||
struct dispmsg_write_memory msg;
|
||||
msg.msgid = DISPMSG_WRITE_MEMORY;
|
||||
msg.device = window->session->device;
|
||||
msg.offset = fb->fb_location;
|
||||
msg.size = fb->datasize;
|
||||
msg.src = fb->data;
|
||||
if ( dispmsg_issue(&msg, sizeof(msg)) == 0 )
|
||||
ret = true;
|
||||
return_buffer(window, fb->data, fb->datasize);
|
||||
free(fb);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint8_t* dispd_get_framebuffer_data(struct dispd_framebuffer* fb)
|
||||
{
|
||||
return fb->data;
|
||||
}
|
||||
|
||||
size_t dispd_get_framebuffer_pitch(struct dispd_framebuffer* fb)
|
||||
{
|
||||
return fb->pitch;
|
||||
}
|
||||
|
||||
int dispd_get_framebuffer_format(struct dispd_framebuffer* fb)
|
||||
{
|
||||
return fb->bpp;
|
||||
}
|
||||
|
||||
int dispd_get_framebuffer_height(struct dispd_framebuffer* fb)
|
||||
{
|
||||
return fb->height;
|
||||
}
|
||||
|
||||
int dispd_get_framebuffer_width(struct dispd_framebuffer* fb)
|
||||
{
|
||||
return fb->width;
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012 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.
|
||||
*
|
||||
* dispd.h
|
||||
* Interface to the Sortix Display Daemon.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_DISPD_H
|
||||
#define INCLUDE_DISPD_H
|
||||
|
||||
#if !defined(__cplusplus)
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct dispd_session;
|
||||
struct dispd_window;
|
||||
struct dispd_framebuffer;
|
||||
|
||||
bool dispd_initialize(int* argc, char*** argv);
|
||||
|
||||
struct dispd_session* dispd_attach_default_session(void);
|
||||
bool dispd_detach_session(struct dispd_session* session);
|
||||
|
||||
bool dispd_session_setup_game_rgba(struct dispd_session* session);
|
||||
|
||||
struct dispd_window* dispd_create_window_game_rgba(struct dispd_session* session);
|
||||
bool dispd_destroy_window(struct dispd_window* window);
|
||||
|
||||
struct dispd_framebuffer* dispd_begin_render(struct dispd_window* window);
|
||||
bool dispd_finish_render(struct dispd_framebuffer* framebuffer);
|
||||
|
||||
uint8_t* dispd_get_framebuffer_data(struct dispd_framebuffer* framebuffer);
|
||||
size_t dispd_get_framebuffer_pitch(struct dispd_framebuffer* framebuffer);
|
||||
int dispd_get_framebuffer_format(struct dispd_framebuffer* framebuffer);
|
||||
int dispd_get_framebuffer_height(struct dispd_framebuffer* framebuffer);
|
||||
int dispd_get_framebuffer_width(struct dispd_framebuffer* framebuffer);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,3 @@
|
|||
display
|
||||
*.o
|
||||
*.inc
|
|
@ -0,0 +1,51 @@
|
|||
SOFTWARE_MEANT_FOR_SORTIX=1
|
||||
include ../build-aux/platform.mak
|
||||
include ../build-aux/compiler.mak
|
||||
include ../build-aux/dirs.mak
|
||||
|
||||
OPTLEVEL?=-g -O2
|
||||
CFLAGS?=$(OPTLEVEL)
|
||||
|
||||
CFLAGS:=$(CFLAGS) -Wall -Wextra
|
||||
|
||||
PROGRAM=display
|
||||
MANPAGES1 = display.1
|
||||
MANPAGES5 = displayrc.5
|
||||
|
||||
OBJS=\
|
||||
connection.o \
|
||||
display-code.o \
|
||||
display.o \
|
||||
server.o \
|
||||
window.o \
|
||||
|
||||
LIBS:=-lui
|
||||
|
||||
all: $(PROGRAM)
|
||||
|
||||
.PHONY: all install clean
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(BINDIR)
|
||||
install $(PROGRAM) $(DESTDIR)$(BINDIR)
|
||||
mkdir -p $(DESTDIR)$(SYSCONFDIR)/default
|
||||
printf '#!sh\nexec terminal\n' > $(DESTDIR)$(SYSCONFDIR)/default/displayrc
|
||||
chmod +x $(DESTDIR)$(SYSCONFDIR)/default/displayrc
|
||||
mkdir -p $(DESTDIR)$(MANDIR)/man1
|
||||
install $(MANPAGES1) $(DESTDIR)$(MANDIR)/man1
|
||||
mkdir -p $(DESTDIR)$(MANDIR)/man5
|
||||
install $(MANPAGES5) $(DESTDIR)$(MANDIR)/man5
|
||||
|
||||
$(PROGRAM): $(OBJS)
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $(OBJS) -o $@ $(LIBS)
|
||||
|
||||
display.o: arrow.inc
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
%.inc: %.rgb
|
||||
carray -cs -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(PROGRAM) *.o *.inc
|
Binary file not shown.
|
@ -0,0 +1,535 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016, 2023 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2023 Juhani 'nortti' Krekelä.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* connection.c
|
||||
* Display protocol implementation.
|
||||
*/
|
||||
|
||||
#include <sys/display.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/termmode.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "display.h"
|
||||
|
||||
void connection_schedule_transmit(struct connection* connection,
|
||||
const void* buffer,
|
||||
size_t count)
|
||||
{
|
||||
assert(connection);
|
||||
size_t available = connection->outgoing_size - connection->outgoing_used;
|
||||
if ( available < count )
|
||||
{
|
||||
// TODO: Overflow.
|
||||
size_t required_size = connection->outgoing_used + count;
|
||||
// TODO: Check allocation.
|
||||
unsigned char* new_outgoing = malloc(required_size);
|
||||
size_t first_available =
|
||||
connection->outgoing_size - connection->outgoing_offset;
|
||||
size_t first = connection->outgoing_used < first_available ?
|
||||
connection->outgoing_used :
|
||||
first_available;
|
||||
if ( connection->outgoing )
|
||||
{
|
||||
memcpy(new_outgoing, connection->outgoing +
|
||||
connection->outgoing_offset, first);
|
||||
size_t second = connection->outgoing_used - first;
|
||||
memcpy(new_outgoing + first, connection->outgoing, second);
|
||||
free(connection->outgoing);
|
||||
}
|
||||
connection->outgoing_offset = 0;
|
||||
connection->outgoing_size = required_size;
|
||||
connection->outgoing = new_outgoing;
|
||||
}
|
||||
|
||||
size_t used_offset =
|
||||
(connection->outgoing_offset + connection->outgoing_used) %
|
||||
connection->outgoing_size;
|
||||
size_t first_available = connection->outgoing_size - used_offset;
|
||||
size_t first = count < first_available ? count : first_available;
|
||||
memcpy(connection->outgoing + used_offset, buffer, first);
|
||||
size_t second = count - first;
|
||||
memcpy(connection->outgoing, (const unsigned char*) buffer + first, second);
|
||||
connection->outgoing_used += count;
|
||||
}
|
||||
|
||||
void connection_schedule_ack_event(struct connection* connection,
|
||||
uint32_t id,
|
||||
int32_t error)
|
||||
{
|
||||
struct event_ack event = { .id = id, .error = error };
|
||||
struct display_packet_header header = { .id = EVENT_ACK,
|
||||
.size = sizeof(event) };
|
||||
connection_schedule_transmit(connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(connection, &event, sizeof(event));
|
||||
}
|
||||
|
||||
void connection_initialize(struct connection* connection,
|
||||
struct display* display,
|
||||
int fd)
|
||||
{
|
||||
memset(connection, 0, sizeof(*connection));
|
||||
connection->display = display;
|
||||
connection->fd = fd;
|
||||
}
|
||||
|
||||
struct window* connection_find_window_raw(struct connection* connection,
|
||||
uint32_t window_id)
|
||||
{
|
||||
if ( MAX_WINDOWS_PER_CONNECTION <= window_id )
|
||||
return NULL;
|
||||
return &connection->windows[window_id];
|
||||
}
|
||||
|
||||
struct window* connection_find_window(struct connection* connection,
|
||||
uint32_t window_id)
|
||||
{
|
||||
struct window* result = connection_find_window_raw(connection, window_id);
|
||||
if ( !result->created )
|
||||
return NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CONNECTION_MESSAGE_HANDLER_NO_AUX(message_name) \
|
||||
void connection_handler_##message_name( \
|
||||
struct connection* connection, \
|
||||
struct display_##message_name* msg, \
|
||||
void* auxiliary __attribute__((unused)), \
|
||||
size_t auxiliary_size __attribute__((unused)), \
|
||||
const struct server* server __attribute__((unused)))
|
||||
|
||||
#define CONNECTION_MESSAGE_HANDLER(message_name) \
|
||||
void connection_handler_##message_name( \
|
||||
struct connection* connection, \
|
||||
struct display_##message_name* msg, \
|
||||
unsigned char* auxiliary, \
|
||||
size_t auxiliary_size, \
|
||||
const struct server* server __attribute__((unused)))
|
||||
|
||||
#define CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(message_name) \
|
||||
void connection_handler_##message_name( \
|
||||
struct connection* connection, \
|
||||
struct display_##message_name* msg, \
|
||||
void* auxiliary __attribute__((unused)), \
|
||||
size_t auxiliary_size __attribute__((unused)), \
|
||||
const struct server* server)
|
||||
|
||||
#define CONNECTION_MESSAGE_HANDLER_SERVER(message_name) \
|
||||
void connection_handler_##message_name( \
|
||||
struct connection* connection, \
|
||||
struct display_##message_name* msg, \
|
||||
unsigned char* auxiliary, \
|
||||
size_t auxiliary_size, \
|
||||
const struct server* server)
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(shutdown)
|
||||
{
|
||||
(void) connection;
|
||||
if ( msg->code == 0 )
|
||||
exit(0);
|
||||
else if ( msg->code == 1 )
|
||||
exit(1);
|
||||
else if ( msg->code == 2 )
|
||||
exit(2);
|
||||
else if ( msg->code == 3 )
|
||||
exit(3);
|
||||
else
|
||||
exit(0);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(create_window)
|
||||
{
|
||||
struct window* window =
|
||||
connection_find_window_raw(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
if ( window->created )
|
||||
return;
|
||||
window_initialize(window, connection, connection->display, msg->window_id);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(destroy_window)
|
||||
{
|
||||
struct window* window = connection_find_window(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
window_destroy(window);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(resize_window)
|
||||
{
|
||||
struct window* window = connection_find_window(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
window_client_resize(window, msg->width, msg->height);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER(render_window)
|
||||
{
|
||||
struct window* window = connection_find_window(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
|
||||
struct framebuffer src;
|
||||
src.xres = msg->width;
|
||||
src.yres = msg->height;
|
||||
src.pitch = msg->width;
|
||||
src.buffer = (uint32_t*) auxiliary;
|
||||
|
||||
if ( auxiliary_size < sizeof(uint32_t) * src.xres * src.yres )
|
||||
return;
|
||||
|
||||
struct framebuffer dst =
|
||||
framebuffer_crop(window_client_buffer(window),
|
||||
msg->left, msg->top, msg->width, msg->height);
|
||||
|
||||
framebuffer_copy_to_framebuffer(dst, src);
|
||||
|
||||
window_schedule_redraw(window);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER(title_window)
|
||||
{
|
||||
struct window* window = connection_find_window(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
|
||||
const char* title = (char*) auxiliary;
|
||||
free(window->title);
|
||||
window->title = strndup(title, auxiliary_size);
|
||||
|
||||
window_render_frame(window);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(show_window)
|
||||
{
|
||||
struct window* window = connection_find_window(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
if ( !window->show )
|
||||
display_schedule_redraw(window->display);
|
||||
window->show = true;
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(hide_window)
|
||||
{
|
||||
struct window* window = connection_find_window(connection, msg->window_id);
|
||||
if ( !window )
|
||||
return;
|
||||
if ( window->show )
|
||||
display_schedule_redraw(window->display);
|
||||
window->show = false;
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_SERVER(chkblayout)
|
||||
{
|
||||
if ( tcsetblob(server->tty_fd, "kblayout", auxiliary, auxiliary_size) < 0 )
|
||||
connection_schedule_ack_event(connection, msg->id, errno);
|
||||
else
|
||||
connection_schedule_ack_event(connection, msg->id, 0);
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX(request_displays)
|
||||
{
|
||||
struct event_displays event;
|
||||
event.id = msg->id;
|
||||
event.displays = 1; // TODO: Multimonitor support.
|
||||
|
||||
struct display_packet_header header;
|
||||
header.id = EVENT_DISPLAYS;
|
||||
header.size = sizeof(event);
|
||||
|
||||
connection_schedule_transmit(connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(connection, &event, sizeof(event));
|
||||
}
|
||||
|
||||
static struct dispmsg_crtc_mode*
|
||||
get_available_modes(const struct tiocgdisplay* display,
|
||||
size_t* modes_count_ptr)
|
||||
{
|
||||
struct dispmsg_get_crtc_modes msg;
|
||||
msg.msgid = DISPMSG_GET_CRTC_MODES;
|
||||
msg.device = display->device;
|
||||
msg.connector = display->connector;
|
||||
size_t guess = 1;
|
||||
while ( true )
|
||||
{
|
||||
struct dispmsg_crtc_mode* ret =
|
||||
calloc(guess, sizeof(struct dispmsg_crtc_mode));
|
||||
if ( !ret )
|
||||
return NULL;
|
||||
msg.modes_length = guess;
|
||||
msg.modes = ret;
|
||||
if ( dispmsg_issue(&msg, sizeof(msg)) == 0 )
|
||||
{
|
||||
*modes_count_ptr = guess;
|
||||
return ret;
|
||||
}
|
||||
free(ret);
|
||||
if ( errno == ERANGE && guess < msg.modes_length )
|
||||
{
|
||||
guess = msg.modes_length;
|
||||
continue;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(request_display_modes)
|
||||
{
|
||||
struct event_display_modes event;
|
||||
event.id = msg->id;
|
||||
event.modes_count = 0;
|
||||
|
||||
struct dispmsg_crtc_mode* modes = NULL;
|
||||
// TODO: Multimonitor support.
|
||||
if ( msg->display_id != 0 )
|
||||
{
|
||||
connection_schedule_ack_event(connection, msg->id, EINVAL);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t modes_count;
|
||||
modes = get_available_modes(&server->display->display, &modes_count);
|
||||
if ( !modes )
|
||||
{
|
||||
connection_schedule_ack_event(connection, msg->id, errno);
|
||||
return;
|
||||
}
|
||||
else if ( (uint32_t) modes_count != modes_count )
|
||||
{
|
||||
free(modes);
|
||||
connection_schedule_ack_event(connection, msg->id, EOVERFLOW);
|
||||
return;
|
||||
}
|
||||
event.modes_count = modes_count;
|
||||
}
|
||||
|
||||
size_t modes_size = event.modes_count * sizeof(struct dispmsg_crtc_mode);
|
||||
|
||||
struct display_packet_header header;
|
||||
header.id = EVENT_DISPLAY_MODES;
|
||||
header.size = sizeof(event) + modes_size;
|
||||
|
||||
connection_schedule_transmit(connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(connection, &event, sizeof(event));
|
||||
connection_schedule_transmit(connection, modes, modes_size);
|
||||
}
|
||||
|
||||
static bool get_current_mode(const struct tiocgdisplay* display,
|
||||
struct dispmsg_crtc_mode* mode)
|
||||
{
|
||||
struct dispmsg_set_crtc_mode msg;
|
||||
msg.msgid = DISPMSG_GET_CRTC_MODE;
|
||||
msg.device = display->device;
|
||||
msg.connector = display->connector;
|
||||
if ( dispmsg_issue(&msg, sizeof(msg)) )
|
||||
return false;
|
||||
*mode = msg.mode;
|
||||
return true;
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(request_display_mode)
|
||||
{
|
||||
struct event_display_mode event;
|
||||
event.id = msg->id;
|
||||
|
||||
// TODO: Multimonitor support.
|
||||
if ( msg->display_id != 0 )
|
||||
{
|
||||
connection_schedule_ack_event(connection, msg->id, EINVAL);
|
||||
return;
|
||||
}
|
||||
else if ( !get_current_mode(&server->display->display, &event.mode) )
|
||||
{
|
||||
connection_schedule_ack_event(connection, msg->id, EINVAL);
|
||||
return;
|
||||
}
|
||||
|
||||
struct display_packet_header header;
|
||||
header.id = EVENT_DISPLAY_MODE;
|
||||
header.size = sizeof(event);
|
||||
|
||||
connection_schedule_transmit(connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(connection, &event, sizeof(event));
|
||||
}
|
||||
|
||||
static bool set_current_mode(const struct tiocgdisplay* display,
|
||||
struct dispmsg_crtc_mode mode)
|
||||
{
|
||||
struct dispmsg_set_crtc_mode msg;
|
||||
msg.msgid = DISPMSG_SET_CRTC_MODE;
|
||||
msg.device = display->device;
|
||||
msg.connector = display->connector;
|
||||
msg.mode = mode;
|
||||
return dispmsg_issue(&msg, sizeof(msg)) == 0;
|
||||
}
|
||||
|
||||
CONNECTION_MESSAGE_HANDLER_NO_AUX_SERVER(set_display_mode)
|
||||
{
|
||||
// TODO: Multimonitor support.
|
||||
if ( msg->display_id != 0 )
|
||||
connection_schedule_ack_event(connection, msg->id, EINVAL);
|
||||
else if ( !set_current_mode(&server->display->display, msg->mode) )
|
||||
connection_schedule_ack_event(connection, msg->id, errno);
|
||||
else
|
||||
connection_schedule_ack_event(connection, msg->id, 0);
|
||||
}
|
||||
|
||||
typedef void (*connection_message_handler)(struct connection* connection,
|
||||
void* msg,
|
||||
void* auxiliary,
|
||||
size_t auxiliary_size,
|
||||
const struct server* server);
|
||||
|
||||
struct connection_message_handler_registration
|
||||
{
|
||||
connection_message_handler handler;
|
||||
size_t message_size;
|
||||
};
|
||||
|
||||
#define REGISTER_CONNECTION_MESSAGE_HANDLER(message_name) \
|
||||
{ (connection_message_handler) connection_handler_##message_name, \
|
||||
sizeof(struct display_##message_name) }
|
||||
|
||||
struct connection_message_handler_registration connection_message_handlers[] =
|
||||
{
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(create_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(destroy_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(resize_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(render_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(title_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(show_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(hide_window),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(shutdown),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(chkblayout),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(request_displays),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(request_display_modes),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(request_display_mode),
|
||||
REGISTER_CONNECTION_MESSAGE_HANDLER(set_display_mode),
|
||||
};
|
||||
|
||||
size_t num_connection_message_handlers = sizeof(connection_message_handlers) /
|
||||
sizeof(connection_message_handlers[0]);
|
||||
|
||||
short connection_interested_poll_events(struct connection* connection)
|
||||
{
|
||||
return POLLIN | (connection->outgoing_used ? POLLOUT : 0);
|
||||
}
|
||||
|
||||
void connection_can_read(struct connection* connection,
|
||||
const struct server* server)
|
||||
{
|
||||
while ( connection->packet_header_received <
|
||||
sizeof(connection->packet_header) )
|
||||
{
|
||||
ssize_t amount = read(connection->fd,
|
||||
(unsigned char*) &connection->packet_header +
|
||||
connection->packet_header_received,
|
||||
sizeof(connection->packet_header) -
|
||||
connection->packet_header_received);
|
||||
if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) )
|
||||
return;
|
||||
if ( amount < 0 || amount == 0 )
|
||||
return; // TODO: No longer signal interest in reading + disconnect.
|
||||
connection->packet_header_received += amount;
|
||||
}
|
||||
|
||||
size_t packet_size = connection->packet_header.size;
|
||||
|
||||
// TODO: Check allocation and protect against too big buffers.
|
||||
if ( !connection->packet )
|
||||
connection->packet = malloc(packet_size);
|
||||
|
||||
while ( connection->packet_received < packet_size )
|
||||
{
|
||||
ssize_t amount = read(connection->fd,
|
||||
connection->packet + connection->packet_received,
|
||||
packet_size - connection->packet_received);
|
||||
if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) )
|
||||
return;
|
||||
if ( amount < 0 || amount == 0 )
|
||||
return; // TODO: No longer signal interest in reading + disconnect.
|
||||
connection->packet_received += amount;
|
||||
}
|
||||
|
||||
size_t packet_id = connection->packet_header.id;
|
||||
|
||||
if ( packet_id < num_connection_message_handlers )
|
||||
{
|
||||
struct connection_message_handler_registration* handler =
|
||||
&connection_message_handlers[packet_id];
|
||||
unsigned char* auxiliary = connection->packet + handler->message_size;
|
||||
size_t auxiliary_size = packet_size - handler->message_size;
|
||||
handler->handler(connection, connection->packet,
|
||||
auxiliary, auxiliary_size, server);
|
||||
}
|
||||
|
||||
connection->packet_header_received = 0;
|
||||
free(connection->packet);
|
||||
connection->packet = NULL;
|
||||
connection->packet_received = 0;
|
||||
|
||||
// TODO: Check if we can receive another packet, but only if we haven't
|
||||
// done so much work that the display server is starved.
|
||||
}
|
||||
|
||||
void connection_can_write(struct connection* connection)
|
||||
{
|
||||
while ( connection->outgoing_used )
|
||||
{
|
||||
size_t available =
|
||||
connection->outgoing_size - connection->outgoing_offset;
|
||||
size_t count = connection->outgoing_used < available ?
|
||||
connection->outgoing_used : available;
|
||||
unsigned char* buf = connection->outgoing + connection->outgoing_offset;
|
||||
ssize_t amount = write(connection->fd, buf, count);
|
||||
if ( amount < 0 && (errno == EAGAIN || errno == EWOULDBLOCK) )
|
||||
return;
|
||||
if ( amount < 0 || amount == 0 )
|
||||
return; // TODO: No longer signal interest in writing + disconnect.
|
||||
connection->outgoing_offset = (connection->outgoing_offset + amount) %
|
||||
connection->outgoing_size;
|
||||
connection->outgoing_used -= amount;
|
||||
}
|
||||
|
||||
free(connection->outgoing);
|
||||
connection->outgoing = NULL;
|
||||
connection->outgoing_used = 0;
|
||||
connection->outgoing_size = 0;
|
||||
}
|
||||
|
||||
void connection_destroy(struct connection* connection)
|
||||
{
|
||||
for ( size_t i = 0; i < MAX_WINDOWS_PER_CONNECTION; i++ )
|
||||
{
|
||||
if ( !connection->windows[i].created )
|
||||
continue;
|
||||
window_destroy(&connection->windows[i]);
|
||||
}
|
||||
close(connection->fd);
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016 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.
|
||||
*
|
||||
* connection.h
|
||||
* Display protocol implementation.
|
||||
*/
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <display-protocol.h>
|
||||
|
||||
#include "server.h"
|
||||
#include "window.h"
|
||||
|
||||
struct display;
|
||||
struct window;
|
||||
|
||||
#define MAX_WINDOWS_PER_CONNECTION 256
|
||||
|
||||
struct connection
|
||||
{
|
||||
struct display* display;
|
||||
struct window windows[MAX_WINDOWS_PER_CONNECTION];
|
||||
struct display_packet_header packet_header;
|
||||
size_t packet_header_received;
|
||||
unsigned char* packet;
|
||||
size_t packet_received;
|
||||
unsigned char* outgoing;
|
||||
size_t outgoing_offset;
|
||||
size_t outgoing_used;
|
||||
size_t outgoing_size;
|
||||
int fd;
|
||||
};
|
||||
|
||||
void connection_schedule_transmit(struct connection* connection,
|
||||
const void* buffer,
|
||||
size_t count);
|
||||
void connection_initialize(struct connection* connection,
|
||||
struct display* display,
|
||||
int fd);
|
||||
struct window* connection_find_window_raw(struct connection* connection,
|
||||
uint32_t window_id);
|
||||
struct window* connection_find_window(struct connection* connection,
|
||||
uint32_t window_id);
|
||||
short connection_interested_poll_events(struct connection* connection);
|
||||
void connection_can_read(struct connection* connection,
|
||||
const struct server* server);
|
||||
void connection_can_write(struct connection* connection);
|
||||
void connection_destroy(struct connection* connection);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016 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.
|
||||
*
|
||||
* damage-rect.c
|
||||
* Damage rectangles.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "damage-rect.h"
|
||||
|
||||
struct damage_rect damage_rect_add(struct damage_rect a, struct damage_rect b)
|
||||
{
|
||||
if ( !a.width || !a.height )
|
||||
return b;
|
||||
if ( !b.width || !b.height )
|
||||
return a;
|
||||
if ( a.left < b.left )
|
||||
b.width += b.left - a.left, b.left = a.left;
|
||||
if ( b.width < a.width )
|
||||
b.width = a.width;
|
||||
if ( a.top < b.top )
|
||||
b.height += b.top - a.top, b.top = a.top;
|
||||
if ( b.height < a.height )
|
||||
b.height = a.height;
|
||||
return b;
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2014, 2015, 2016 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
|
||||
|
@ -13,21 +13,23 @@
|
|||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* library.c
|
||||
* Main entry point of the Sortix Display Daemon.
|
||||
* damage-rect.h
|
||||
* Damage rectangles.
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#ifndef DAMAGE_RECT_H
|
||||
#define DAMAGE_RECT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <dispd.h>
|
||||
|
||||
#include "session.h"
|
||||
|
||||
bool dispd_initialize(int* argc, char*** argv)
|
||||
struct damage_rect
|
||||
{
|
||||
if ( !dispd__session_initialize(argc, argv) )
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
size_t left;
|
||||
size_t top;
|
||||
size_t width;
|
||||
size_t height;
|
||||
};
|
||||
|
||||
struct damage_rect damage_rect_add(struct damage_rect a, struct damage_rect b);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,917 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016, 2018, 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.
|
||||
*
|
||||
* display-code.c
|
||||
* Display server logic.
|
||||
*/
|
||||
|
||||
#include <sys/display.h>
|
||||
#include <sys/keycodes.h>
|
||||
#include <sys/ps2mouse.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <timespec.h>
|
||||
|
||||
#include <display-protocol.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "display.h"
|
||||
#include "pixel.h"
|
||||
#include "vgafont.h"
|
||||
#include "window.h"
|
||||
|
||||
extern struct framebuffer arrow_framebuffer;
|
||||
|
||||
void display_initialize(struct display* display)
|
||||
{
|
||||
memset(display, 0, sizeof(*display));
|
||||
display->redraw = true;
|
||||
}
|
||||
|
||||
void assert_is_well_formed_display_list(struct display* display)
|
||||
{
|
||||
struct window* last_window = NULL;
|
||||
struct window* iterator = display->bottom_window;
|
||||
while ( iterator )
|
||||
{
|
||||
assert(iterator->below_window == last_window);
|
||||
last_window = iterator;
|
||||
iterator = iterator->above_window;
|
||||
}
|
||||
assert(last_window == display->top_window);
|
||||
}
|
||||
|
||||
void assert_is_well_formed_display(struct display* display)
|
||||
{
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
bool found_active_window = display->active_window == NULL;
|
||||
bool found_tab_candidate = display->tab_candidate == NULL;
|
||||
struct window* iterator = display->bottom_window;
|
||||
while ( iterator )
|
||||
{
|
||||
if ( iterator == display->active_window )
|
||||
found_active_window = true;
|
||||
if ( iterator == display->tab_candidate )
|
||||
found_tab_candidate = true;
|
||||
iterator = iterator->above_window;
|
||||
}
|
||||
assert(found_active_window);
|
||||
assert(found_tab_candidate);
|
||||
}
|
||||
|
||||
void display_link_window_at_top(struct display* display, struct window* window)
|
||||
{
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
assert(!window->above_window);
|
||||
assert(!window->below_window);
|
||||
assert(display->top_window != window);
|
||||
assert(display->bottom_window != window);
|
||||
|
||||
if ( (window->below_window = display->top_window) )
|
||||
window->below_window->above_window = window;
|
||||
window->above_window = NULL;
|
||||
|
||||
display->top_window = window;
|
||||
if ( !display->bottom_window )
|
||||
display->bottom_window = window;
|
||||
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
display_schedule_redraw(display);
|
||||
}
|
||||
|
||||
void display_unlink_window(struct display* display, struct window* window)
|
||||
{
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
assert(window->below_window || display->bottom_window == window);
|
||||
assert(window->above_window || display->top_window == window);
|
||||
|
||||
if ( window->below_window )
|
||||
window->below_window->above_window = window->above_window;
|
||||
else
|
||||
display->bottom_window = window->above_window;
|
||||
if ( window->above_window )
|
||||
window->above_window->below_window = window->below_window;
|
||||
else
|
||||
display->top_window = window->below_window;
|
||||
|
||||
assert(display->bottom_window != window);
|
||||
assert(display->top_window != window);
|
||||
|
||||
window->above_window = NULL;
|
||||
window->below_window = NULL;
|
||||
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
display_schedule_redraw(display);
|
||||
}
|
||||
|
||||
void display_unlink_window_removal(struct display* display,
|
||||
struct window* window)
|
||||
{
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
if ( display->tab_candidate == window )
|
||||
if ( !(display->tab_candidate = window->below_window) )
|
||||
if ( (display->tab_candidate = display->top_window) == window )
|
||||
display->tab_candidate = NULL;
|
||||
|
||||
if ( display->active_window == window )
|
||||
display->active_window = NULL;
|
||||
|
||||
window->focus = false;
|
||||
display_schedule_redraw(display);
|
||||
|
||||
assert_is_well_formed_display_list(display);
|
||||
|
||||
display_unlink_window(display, window);
|
||||
|
||||
assert_is_well_formed_display_list(display);
|
||||
}
|
||||
|
||||
void display_unmark_active_window(struct display* display,
|
||||
struct window* window)
|
||||
{
|
||||
assert(display->active_window == window);
|
||||
window->focus = false;
|
||||
display->active_window = NULL;
|
||||
window_render_frame(window);
|
||||
}
|
||||
|
||||
void display_mark_active_window(struct display* display, struct window* window)
|
||||
{
|
||||
assert(!display->active_window);
|
||||
window->focus = true;
|
||||
display->active_window = window;
|
||||
window_render_frame(window);
|
||||
}
|
||||
|
||||
void display_update_active_window(struct display* display)
|
||||
{
|
||||
if ( !display->active_window && display->top_window )
|
||||
display_mark_active_window(display, display->top_window);
|
||||
}
|
||||
|
||||
void display_move_window_to_top(struct display* display, struct window* window)
|
||||
{
|
||||
display_unlink_window(display, window);
|
||||
display_link_window_at_top(display, window);
|
||||
}
|
||||
|
||||
void display_change_active_window(struct display* display,
|
||||
struct window* window)
|
||||
{
|
||||
if ( display->active_window == window )
|
||||
{
|
||||
display_move_window_to_top(display, window);
|
||||
return;
|
||||
}
|
||||
|
||||
display_unmark_active_window(display, display->active_window);
|
||||
display_mark_active_window(display, window);
|
||||
}
|
||||
|
||||
void display_set_active_window(struct display* display, struct window* window)
|
||||
{
|
||||
display_change_active_window(display, window);
|
||||
display_move_window_to_top(display, window);
|
||||
}
|
||||
|
||||
void display_add_window(struct display* display, struct window* window)
|
||||
{
|
||||
display_link_window_at_top(display, window);
|
||||
display_update_active_window(display);
|
||||
}
|
||||
|
||||
void display_remove_window(struct display* display, struct window* window)
|
||||
{
|
||||
display_unlink_window_removal(display, window);
|
||||
display_update_active_window(display);
|
||||
|
||||
assert(display->top_window != window);
|
||||
assert(display->bottom_window != window);
|
||||
struct window* last_window = NULL;
|
||||
struct window* iterator = display->bottom_window;
|
||||
while ( iterator )
|
||||
{
|
||||
assert(iterator != window);
|
||||
assert(iterator->below_window == last_window);
|
||||
last_window = iterator;
|
||||
iterator = iterator->above_window;
|
||||
}
|
||||
assert(last_window == display->top_window);
|
||||
|
||||
assert(!display->top_window || display->active_window);
|
||||
}
|
||||
|
||||
union c { struct { uint8_t b; uint8_t g; uint8_t r; }; uint32_t v; };
|
||||
|
||||
static void wallpaper(struct framebuffer fb)
|
||||
{
|
||||
static uint32_t s;
|
||||
static uint32_t t;
|
||||
static bool seeded = false;
|
||||
if ( !seeded )
|
||||
{
|
||||
s = arc4random();
|
||||
t = arc4random();
|
||||
seeded = true;
|
||||
}
|
||||
for ( size_t y = 0; y < fb.yres; y++ )
|
||||
{
|
||||
for ( size_t x = 0; x < fb.xres; x++ )
|
||||
{
|
||||
uint32_t r = 3793 * x + 6959 * y + 1889 * t + 7901 * s;
|
||||
r ^= (5717 * x * 2953 * y) ^ s ^ t;
|
||||
r = (r >> 24) ^ (r >> 16) ^ (r >> 8) ^ r;
|
||||
union c c;
|
||||
if ( x && (r & 0x3) == 2 )
|
||||
c.v = framebuffer_get_pixel(fb, x - 1, y);
|
||||
else if ( y && (r & 0x3) == 1 )
|
||||
c.v = framebuffer_get_pixel(fb, x, y - 1);
|
||||
else if ( x && y )
|
||||
c.v = framebuffer_get_pixel(fb, x - 1, y - 1);
|
||||
else
|
||||
{
|
||||
c.v = t;
|
||||
c.r = (c.r & 0xc0) | (r >> 0 & 0x3f);
|
||||
c.g = (c.g & 0xc0) | (r >> 4 & 0x3f);
|
||||
c.b = (c.b & 0xc0) | (r >> 8 & 0x3f);
|
||||
}
|
||||
if ( (r & 0xf0) == 0x10 && c.r ) c.r--;
|
||||
if ( (r & 0xf0) == 0x20 && c.g ) c.g--;
|
||||
if ( (r & 0xf0) == 0x30 && c.b ) c.b--;
|
||||
if ( (r & 0xf0) == 0x40 && c.r != 255 ) c.r++;
|
||||
if ( (r & 0xf0) == 0x50 && c.g != 255 ) c.g++;
|
||||
if ( (r & 0xf0) == 0x60 && c.b != 255 ) c.b++;
|
||||
union c tc = {.v = t};
|
||||
if ( c.r && c.r - tc.r > (int8_t) (r >> 0) + 64 ) c.r--;
|
||||
if ( c.r != 255 && tc.r - c.r > (int8_t) (r >> 4) + 240 ) c.r++;
|
||||
if ( c.g && c.g - tc.g > (int8_t) (r >> 8) + 64) c.g--;
|
||||
if ( c.g != 255 && tc.g - c.g > (int8_t) (r >> 12) + 240 ) c.g++;
|
||||
if ( c.b && c.b - tc.b > (int8_t) (r >> 16) + 64 ) c.b--;
|
||||
if ( c.b != 255 && tc.b - c.b > (int8_t) (r >> 20) + 240 ) c.b++;
|
||||
framebuffer_set_pixel(fb, x, y, c.v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void display_composit(struct display* display, struct framebuffer fb)
|
||||
{
|
||||
struct damage_rect damage_rect = display->damage_rect;
|
||||
damage_rect.left = 0;
|
||||
damage_rect.top = 0;
|
||||
damage_rect.width = fb.xres;
|
||||
damage_rect.height = fb.yres;
|
||||
if ( !damage_rect.width || !damage_rect.height )
|
||||
return;
|
||||
|
||||
#if 0
|
||||
uint32_t bg_color = make_color(0x89 * 2/3, 0xc7 * 2/3, 0xff * 2/3);
|
||||
for ( size_t y = 0; y < damage_rect.height; y++ )
|
||||
for ( size_t x = 0; x < damage_rect.width; x++ )
|
||||
framebuffer_set_pixel(fb, damage_rect.left + x, damage_rect.top + y,
|
||||
bg_color);
|
||||
#endif
|
||||
|
||||
framebuffer_copy_to_framebuffer(fb, display->wallpaper);
|
||||
|
||||
for ( struct window* window = display->bottom_window;
|
||||
window;
|
||||
window = window->above_window )
|
||||
{
|
||||
if ( !window->show )
|
||||
continue;
|
||||
|
||||
size_t winfb_left;
|
||||
size_t winfb_top;
|
||||
struct framebuffer winfb = window->buffer;
|
||||
|
||||
if ( window->left < 0 )
|
||||
{
|
||||
winfb_left = 0;
|
||||
winfb = framebuffer_crop(winfb, -window->left, 0,
|
||||
winfb.xres, winfb.yres);
|
||||
}
|
||||
else
|
||||
winfb_left = window->left;
|
||||
|
||||
if ( window->top < 0 )
|
||||
{
|
||||
winfb_top = 0;
|
||||
winfb = framebuffer_crop(winfb, 0, -window->top,
|
||||
winfb.xres, winfb.yres);
|
||||
}
|
||||
else
|
||||
winfb_top = window->top;
|
||||
|
||||
size_t winfb_width = winfb.xres;
|
||||
size_t winfb_height = winfb.yres;
|
||||
|
||||
#if 0
|
||||
if ( winfb_left < damage_rect.left &&
|
||||
winfb_width < damage_rect.left - winfb_left )
|
||||
continue;
|
||||
if ( winfb_left < damage_rect.left )
|
||||
{
|
||||
winfb_left = damage_rect.left;
|
||||
winfb_width -= damage_rect.left - winfb_left;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct framebuffer fb_dst =
|
||||
framebuffer_crop(fb, winfb_left, winfb_top,
|
||||
winfb_width, winfb_height);
|
||||
|
||||
framebuffer_copy_to_framebuffer_blend(fb_dst, winfb);
|
||||
}
|
||||
|
||||
const char* cursor_text = NULL;
|
||||
switch ( display->mouse_state )
|
||||
{
|
||||
case MOUSE_STATE_NONE: break;
|
||||
case MOUSE_STATE_IGNORE: break;
|
||||
case MOUSE_STATE_BUTTON_PRESS: break;
|
||||
case MOUSE_STATE_TITLE_MOVE: break;
|
||||
case MOUSE_STATE_RESIZE_BOTTOM: cursor_text = "↓"; break;
|
||||
case MOUSE_STATE_RESIZE_BOTTOM_LEFT: cursor_text = "└"; break;
|
||||
case MOUSE_STATE_RESIZE_BOTTOM_RIGHT: cursor_text = "┘"; break;
|
||||
case MOUSE_STATE_RESIZE_LEFT: cursor_text = "←"; break;
|
||||
case MOUSE_STATE_RESIZE_RIGHT: cursor_text = "→"; break;
|
||||
case MOUSE_STATE_RESIZE_TOP: cursor_text = "↑"; break;
|
||||
case MOUSE_STATE_RESIZE_TOP_LEFT: cursor_text = "┌"; break;
|
||||
case MOUSE_STATE_RESIZE_TOP_RIGHT: cursor_text = "┐"; break;
|
||||
}
|
||||
|
||||
int pointer_hwidth = arrow_framebuffer.xres / 2;
|
||||
int pointer_hheight = arrow_framebuffer.yres / 2;
|
||||
|
||||
int pointer_x = display->pointer_x - (cursor_text ? 0 : pointer_hwidth);
|
||||
int pointer_y = display->pointer_y - (cursor_text ? 0 : pointer_hheight);
|
||||
|
||||
struct framebuffer arrow_render = arrow_framebuffer;
|
||||
if ( pointer_x < 0 )
|
||||
{
|
||||
arrow_render = framebuffer_crop(arrow_render, -pointer_x, 0,
|
||||
arrow_render.xres, arrow_render.yres);
|
||||
pointer_x = 0;
|
||||
}
|
||||
if ( pointer_y < 0 )
|
||||
{
|
||||
arrow_render = framebuffer_crop(arrow_render, 0, -pointer_y,
|
||||
arrow_render.xres, arrow_render.yres);
|
||||
pointer_y = 0;
|
||||
}
|
||||
|
||||
struct framebuffer fb_dst =
|
||||
framebuffer_crop(fb, pointer_x, pointer_y, fb.xres, fb.yres);
|
||||
|
||||
if ( cursor_text != NULL )
|
||||
render_text(fb_dst, cursor_text, make_color(0, 0, 0));
|
||||
else
|
||||
framebuffer_copy_to_framebuffer_blend(fb_dst, arrow_render);
|
||||
|
||||
memset(&damage_rect, 0, sizeof(damage_rect));
|
||||
}
|
||||
|
||||
void display_schedule_redraw(struct display* display)
|
||||
{
|
||||
display->redraw = true;
|
||||
}
|
||||
|
||||
void display_render(struct display* display)
|
||||
{
|
||||
if ( !display->redraw )
|
||||
return;
|
||||
display->redraw = false;
|
||||
|
||||
struct dispmsg_get_crtc_mode get_mode_msg = {0};
|
||||
get_mode_msg.msgid = DISPMSG_GET_CRTC_MODE;
|
||||
get_mode_msg.device = display->display.device;
|
||||
get_mode_msg.connector = display->display.connector;
|
||||
if ( dispmsg_issue(&get_mode_msg, sizeof(get_mode_msg)) != 0 )
|
||||
err(1, "dispmsg_issue: dispmsg_get_crtc_mode");
|
||||
struct dispmsg_crtc_mode mode = get_mode_msg.mode;
|
||||
|
||||
if ( !(mode.control & DISPMSG_CONTROL_VALID) )
|
||||
errx(1, "No valid video mode was set");
|
||||
if ( mode.control & DISPMSG_CONTROL_VGA )
|
||||
errx(1, "A VGA text mode was set");
|
||||
if ( mode.fb_format != 32 )
|
||||
errx(1, "A 32-bit video mode wasn't set");
|
||||
|
||||
size_t framebuffer_length = mode.view_xres * mode.view_yres;
|
||||
size_t framebuffer_size = sizeof(uint32_t) * framebuffer_length;
|
||||
|
||||
if ( display->fb_size != framebuffer_size )
|
||||
{
|
||||
display->fb.buffer = realloc(display->fb.buffer, framebuffer_size);
|
||||
display->fb.xres = mode.view_xres;
|
||||
display->fb.yres = mode.view_yres;
|
||||
display->fb.pitch = mode.view_xres;
|
||||
display->fb_size = framebuffer_size;
|
||||
}
|
||||
|
||||
if ( display->wallpaper_size != framebuffer_size )
|
||||
{
|
||||
display->wallpaper.buffer =
|
||||
realloc(display->wallpaper.buffer, framebuffer_size);
|
||||
display->wallpaper.xres = mode.view_xres;
|
||||
display->wallpaper.yres = mode.view_yres;
|
||||
display->wallpaper.pitch = mode.view_xres;
|
||||
display->wallpaper_size = framebuffer_size;
|
||||
}
|
||||
|
||||
display_on_resolution_change(display, mode.view_xres, mode.view_yres);
|
||||
|
||||
display_composit(display, display->fb);
|
||||
|
||||
struct dispmsg_write_memory write_memory_msg = {0};
|
||||
write_memory_msg.msgid = DISPMSG_WRITE_MEMORY;
|
||||
write_memory_msg.device = display->display.device;
|
||||
write_memory_msg.size = framebuffer_size;
|
||||
write_memory_msg.src = (uint8_t*) display->fb.buffer;
|
||||
if ( dispmsg_issue(&write_memory_msg, sizeof(write_memory_msg)) != 0 )
|
||||
err(1, "dispmsg_issue: dispmsg_write_memory");
|
||||
}
|
||||
|
||||
void display_keyboard_event(struct display* display, uint32_t codepoint)
|
||||
{
|
||||
struct window* window = display->active_window;
|
||||
|
||||
int kbkey = KBKEY_DECODE(codepoint);
|
||||
int abskbkey = kbkey < 0 ? -kbkey : kbkey;
|
||||
|
||||
if ( kbkey && (!window || !window->grab_input) )
|
||||
{
|
||||
switch ( abskbkey )
|
||||
{
|
||||
case KBKEY_LCTRL: display->key_lctrl = kbkey > 0; break;
|
||||
case KBKEY_LALT: display->key_lalt = kbkey > 0; break;
|
||||
case KBKEY_LSUPER: display->key_lsuper = kbkey > 0; break;
|
||||
case KBKEY_RSUPER: display->key_rsuper = kbkey > 0; break;
|
||||
}
|
||||
if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_DELETE )
|
||||
exit(0);
|
||||
if ( display->key_lctrl && display->key_lalt && kbkey == KBKEY_T )
|
||||
{
|
||||
if ( !fork() )
|
||||
{
|
||||
execlp("terminal", "terminal", (char*) NULL);
|
||||
_exit(127);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if ( display->key_lctrl && display->key_lalt && kbkey == -KBKEY_T )
|
||||
return;
|
||||
}
|
||||
|
||||
if ( kbkey && window && !window->grab_input )
|
||||
{
|
||||
// TODO: Ctrl+Q when termninal has a way of handling it or not.
|
||||
if ( (display->key_lalt && kbkey == KBKEY_F4) /* ||
|
||||
(display->key_lctrl && kbkey == KBKEY_Q)*/ )
|
||||
{
|
||||
window_quit(window);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( display->key_lalt && kbkey == KBKEY_F10 )
|
||||
{
|
||||
window_toggle_maximized(display->active_window);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( display->key_lalt && kbkey == KBKEY_TAB )
|
||||
{
|
||||
if ( !display->tab_candidate )
|
||||
display->tab_candidate = display->active_window;
|
||||
struct window* old_candidate = display->tab_candidate;
|
||||
if ( !(display->tab_candidate = old_candidate->below_window) )
|
||||
display->tab_candidate = display->top_window;
|
||||
window_render_frame(old_candidate);
|
||||
window_render_frame(display->tab_candidate);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( kbkey == -KBKEY_LALT && display->tab_candidate )
|
||||
{
|
||||
if ( display->tab_candidate != display->active_window )
|
||||
display_set_active_window(display, display->tab_candidate);
|
||||
display->tab_candidate = NULL;
|
||||
window = display->active_window;
|
||||
return;
|
||||
}
|
||||
if ( display->key_lsuper || display->key_lsuper )
|
||||
{
|
||||
struct window* window = display->active_window;
|
||||
if ( kbkey == KBKEY_LEFT )
|
||||
{
|
||||
window_tile_leftward(window);
|
||||
return;
|
||||
}
|
||||
if ( kbkey == KBKEY_RIGHT )
|
||||
{
|
||||
window_tile_rightward(window);
|
||||
return;
|
||||
}
|
||||
if ( kbkey == KBKEY_UP )
|
||||
{
|
||||
window_tile_up(window);
|
||||
return;
|
||||
}
|
||||
if ( kbkey == KBKEY_DOWN )
|
||||
{
|
||||
window_tile_down(window);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char* grab_inputbed_string = " - Input Grabbed";
|
||||
if ( kbkey == KBKEY_F11 && window && !window->grab_input )
|
||||
{
|
||||
// TODO: window->title can be null.
|
||||
char* new_title;
|
||||
if ( 0 <= asprintf(&new_title, "%s%s", window->title,
|
||||
grab_inputbed_string) )
|
||||
{
|
||||
window->grab_input = true;
|
||||
free(window->title);
|
||||
window->title = new_title;
|
||||
window_render_frame(window);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if ( kbkey == KBKEY_F12 && window && window->grab_input )
|
||||
{
|
||||
// TODO: Only remove from title if there.
|
||||
size_t grab_inputbed_string_len = strlen(grab_inputbed_string);
|
||||
window->title[strlen(window->title) - grab_inputbed_string_len] = '\0';
|
||||
window->grab_input = false;
|
||||
window_render_frame(window);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !window )
|
||||
return;
|
||||
|
||||
struct event_keyboard event;
|
||||
event.window_id = display->active_window->window_id;
|
||||
event.codepoint = codepoint;
|
||||
|
||||
struct display_packet_header header;
|
||||
header.id = EVENT_KEYBOARD;
|
||||
header.size = sizeof(event);
|
||||
|
||||
assert(window->connection);
|
||||
|
||||
connection_schedule_transmit(window->connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(window->connection, &event, sizeof(event));
|
||||
}
|
||||
|
||||
void display_mouse_event(struct display* display, uint8_t byte)
|
||||
{
|
||||
if ( display->mouse_byte_count == 0 && !(byte & MOUSE_ALWAYS_1) )
|
||||
return;
|
||||
if ( display->mouse_byte_count < MOUSE_PACKET_SIZE )
|
||||
display->mouse_bytes[display->mouse_byte_count++] = byte;
|
||||
if ( display->mouse_byte_count < MOUSE_PACKET_SIZE )
|
||||
return;
|
||||
display->mouse_byte_count = 0;
|
||||
uint8_t* bytes = display->mouse_bytes;
|
||||
|
||||
int xm = MOUSE_X(bytes);
|
||||
int ym = MOUSE_Y(bytes);
|
||||
|
||||
int old_pointer_x = display->pointer_x;
|
||||
int old_pointer_y = display->pointer_y;
|
||||
|
||||
if ( xm*xm + ym*ym >= 2*2 )
|
||||
{
|
||||
xm *= 2;
|
||||
ym *= 2;
|
||||
}
|
||||
else if ( xm*xm + ym*ym >= 5*5 )
|
||||
{
|
||||
xm *= 3;
|
||||
ym *= 3;
|
||||
}
|
||||
display->pointer_x += xm;
|
||||
display->pointer_y += ym;
|
||||
if ( xm || ym )
|
||||
display_schedule_redraw(display);
|
||||
|
||||
bool clipped_edge = false;
|
||||
if ( display->pointer_x < 0 )
|
||||
{
|
||||
display->pointer_x = 0;
|
||||
clipped_edge = true;
|
||||
}
|
||||
if ( display->pointer_y < 0 )
|
||||
{
|
||||
display->pointer_y = 0;
|
||||
clipped_edge = true;
|
||||
}
|
||||
if ( display->screen_width < (size_t) display->pointer_x )
|
||||
{
|
||||
display->pointer_x = display->screen_width;
|
||||
clipped_edge = true;
|
||||
}
|
||||
if ( display->screen_height < (size_t) display->pointer_y )
|
||||
{
|
||||
display->pointer_y = display->screen_height;
|
||||
clipped_edge = true;
|
||||
}
|
||||
xm = display->pointer_x - old_pointer_x;
|
||||
ym = display->pointer_y - old_pointer_y;
|
||||
|
||||
struct window* window;
|
||||
for ( window = display->top_window; window; window = window->below_window )
|
||||
{
|
||||
if ( display->mouse_state != MOUSE_STATE_NONE )
|
||||
break;
|
||||
int grace = RESIZE_GRACE;
|
||||
if ( window->window_state == WINDOW_STATE_MAXIMIZED )
|
||||
grace = 0;
|
||||
if ( old_pointer_x < window->left - grace )
|
||||
continue;
|
||||
if ( old_pointer_y < window->top - grace )
|
||||
continue;
|
||||
if ( old_pointer_x > window->left + (ssize_t) window->width + grace )
|
||||
continue;
|
||||
if ( old_pointer_y > window->top + (ssize_t) window->height + grace)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
if ( !window )
|
||||
return;
|
||||
|
||||
ssize_t window_pointer_x = display->pointer_x - window->left;
|
||||
ssize_t window_pointer_y = display->pointer_y - window->top;
|
||||
|
||||
if ( display->active_window != window )
|
||||
{
|
||||
if ( bytes[0] & (MOUSE_BUTTON_LEFT | MOUSE_BUTTON_MIDDLE |
|
||||
MOUSE_BUTTON_RIGHT) )
|
||||
{
|
||||
// TODO: Exit mouse from the current window.
|
||||
display_set_active_window(display, window);
|
||||
}
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
bool maximized = window->window_state != WINDOW_STATE_REGULAR;
|
||||
|
||||
int b2 = 2;
|
||||
int t0 = TITLE_HEIGHT;
|
||||
|
||||
size_t border_width = maximized ? 0 : b2 + 1;
|
||||
size_t button_area_height = maximized ? t0 : t0 - (b2 + 1);
|
||||
size_t button_area_width = button_area_height;
|
||||
size_t button_area_top = maximized ? 0 : b2;
|
||||
ssize_t buttons_x = window->width - border_width - button_area_width*3 + 1;
|
||||
|
||||
bool mouse_on_title = 0 <= window_pointer_x &&
|
||||
window_pointer_x < (ssize_t) window->width &&
|
||||
0 <= window_pointer_y &&
|
||||
window_pointer_y <= (ssize_t) TITLE_HEIGHT;
|
||||
|
||||
for ( size_t n = 0; n < 3; n++ )
|
||||
{
|
||||
ssize_t bottom = button_area_top + button_area_height;
|
||||
ssize_t left = button_area_width * n;
|
||||
ssize_t right = button_area_width * (n + 1);
|
||||
if ( (ssize_t) button_area_top <= window_pointer_y &&
|
||||
window_pointer_y <= bottom &&
|
||||
left <= window_pointer_x - buttons_x &&
|
||||
window_pointer_x - buttons_x < right )
|
||||
{
|
||||
if ( display->mouse_state == MOUSE_STATE_NONE &&
|
||||
(bytes[0] & MOUSE_BUTTON_LEFT) )
|
||||
{
|
||||
display->mouse_state = MOUSE_STATE_BUTTON_PRESS;
|
||||
window->button_states[n] = BUTTON_STATE_PRESSED;
|
||||
window_render_frame(window);
|
||||
}
|
||||
else if ( display->mouse_state == MOUSE_STATE_BUTTON_PRESS &&
|
||||
window->button_states[n] == BUTTON_STATE_PRESSED &&
|
||||
!(bytes[0] & MOUSE_BUTTON_LEFT) )
|
||||
{
|
||||
window->button_states[n] = BUTTON_STATE_NORMAL;
|
||||
window_render_frame(window);
|
||||
switch ( n )
|
||||
{
|
||||
case 0: /* TODO: Minimize. */ break;
|
||||
case 1: window_toggle_maximized(window); break;
|
||||
case 2: window_quit(window); break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if ( display->mouse_state == MOUSE_STATE_NONE &&
|
||||
window->button_states[n] != BUTTON_STATE_HOVER )
|
||||
{
|
||||
window->button_states[n] = BUTTON_STATE_HOVER;
|
||||
window_render_frame(window);
|
||||
}
|
||||
}
|
||||
else if ( window->button_states[n] != BUTTON_STATE_NORMAL )
|
||||
{
|
||||
window->button_states[n] = BUTTON_STATE_NORMAL;
|
||||
if ( display->mouse_state != MOUSE_STATE_NONE )
|
||||
display->mouse_state = MOUSE_STATE_IGNORE;
|
||||
window_render_frame(window);
|
||||
}
|
||||
}
|
||||
|
||||
struct timespec double_click = timespec_make(0, 500000000);
|
||||
|
||||
if ( bytes[0] & MOUSE_BUTTON_LEFT )
|
||||
{
|
||||
if ( (display->mouse_state == MOUSE_STATE_NONE) )
|
||||
{
|
||||
// TODO: Stay in state until mouse release.
|
||||
if ( display->key_lalt )
|
||||
display->mouse_state = MOUSE_STATE_TITLE_MOVE;
|
||||
else if ( mouse_on_title && window_pointer_x < buttons_x )
|
||||
{
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
struct timespec elapsed =
|
||||
timespec_sub(now, window->title_click_time);
|
||||
if ( 0 <= window->title_click_time.tv_sec &&
|
||||
timespec_le(elapsed, double_click) )
|
||||
{
|
||||
display->mouse_state = MOUSE_STATE_IGNORE;
|
||||
window_toggle_maximized(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Reset this if clicked anywhere else or if the
|
||||
// active window changes.
|
||||
window->title_click_time = now;
|
||||
display->mouse_state = MOUSE_STATE_TITLE_MOVE;
|
||||
}
|
||||
}
|
||||
else if ( window_pointer_x < 0 && window_pointer_y < 0 )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_TOP_LEFT;
|
||||
else if ( window_pointer_x < 0 &&
|
||||
0 <= window_pointer_y &&
|
||||
window_pointer_y < (ssize_t) window->height )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_LEFT;
|
||||
else if ( window_pointer_x < 0 &&
|
||||
(ssize_t) window->height <= window_pointer_y )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM_LEFT;
|
||||
else if ( 0 <= window_pointer_x &&
|
||||
window_pointer_x < (ssize_t) window->width &&
|
||||
window_pointer_y < 0 )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_TOP;
|
||||
else if ( 0 <= window_pointer_x &&
|
||||
window_pointer_x < (ssize_t) window->width &&
|
||||
(ssize_t) window->height < window_pointer_y )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM;
|
||||
else if ( (ssize_t) window->width <= window_pointer_x &&
|
||||
window_pointer_y < 0 )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_TOP_RIGHT;
|
||||
else if ( (ssize_t) window->width < window_pointer_x &&
|
||||
0 <= window_pointer_y &&
|
||||
window_pointer_y < (ssize_t) window->height )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_RIGHT;
|
||||
else if ( (ssize_t) window->width <= window_pointer_x &&
|
||||
(ssize_t) window->height <= window_pointer_y )
|
||||
display->mouse_state = MOUSE_STATE_RESIZE_BOTTOM_RIGHT;
|
||||
if ( display->mouse_state != MOUSE_STATE_NONE &&
|
||||
display->mouse_state != MOUSE_STATE_IGNORE )
|
||||
display_schedule_redraw(display);
|
||||
}
|
||||
if ( xm || ym )
|
||||
{
|
||||
bool floating = window->window_state == WINDOW_STATE_REGULAR;
|
||||
bool on_edge =
|
||||
display->pointer_x == 0 ||
|
||||
display->pointer_y == 0 ||
|
||||
display->pointer_x == (ssize_t) display->screen_width ||
|
||||
display->pointer_y == (ssize_t) display->screen_height;
|
||||
switch ( display->mouse_state )
|
||||
{
|
||||
case MOUSE_STATE_NONE: break;
|
||||
case MOUSE_STATE_IGNORE: break;
|
||||
case MOUSE_STATE_BUTTON_PRESS: break;
|
||||
case MOUSE_STATE_TITLE_MOVE:
|
||||
if ( clipped_edge )
|
||||
{
|
||||
ssize_t x = display->pointer_x;
|
||||
ssize_t y = display->pointer_y;
|
||||
ssize_t sw = display->screen_width;
|
||||
ssize_t sh = display->screen_height;
|
||||
ssize_t corner_size = (sw < sh ? sw : sh) / 4;
|
||||
if ( x < corner_size && y < corner_size )
|
||||
window_tile_top_left(window);
|
||||
else if ( sw - x < corner_size && y < corner_size )
|
||||
window_tile_top_right(window);
|
||||
else if ( x < corner_size && sh - y < corner_size )
|
||||
window_tile_bottom_left(window);
|
||||
else if ( sw - x < corner_size && sh - y < corner_size )
|
||||
window_tile_bottom_right(window);
|
||||
else if ( x == 0 )
|
||||
window_tile_left(window);
|
||||
else if ( x == sw )
|
||||
window_tile_right(window);
|
||||
else if ( y == 0 )
|
||||
window_tile_top(window);
|
||||
else if ( y == sh )
|
||||
window_tile_bottom(window);
|
||||
}
|
||||
else if ( floating || !on_edge )
|
||||
{
|
||||
if ( !floating )
|
||||
{
|
||||
// The current behaviour of window_restore becomes
|
||||
// awkward with tiling gestures. I could change the
|
||||
// function itself, especially since this is currently
|
||||
// its only callsite, but the old behaviour could be
|
||||
// nice for a future untile hotkey. Thus, this hack.
|
||||
window_restore(window);
|
||||
window->top = display->pointer_y - TITLE_HEIGHT / 2;
|
||||
window->left = display->pointer_x - window->width / 2;
|
||||
}
|
||||
window_move(window, window->left + xm, window->top + ym);
|
||||
}
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_TOP_LEFT:
|
||||
window_drag_resize(window, xm, ym, -xm, -ym);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_LEFT:
|
||||
window_drag_resize(window, xm, 0, -xm, 0);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_BOTTOM_LEFT:
|
||||
window_drag_resize(window, xm, 0, -xm, ym);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_TOP:
|
||||
window_drag_resize(window, 0, ym, 0, -ym);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_BOTTOM:
|
||||
window_drag_resize(window, 0, 0, 0, ym);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_TOP_RIGHT:
|
||||
window_drag_resize(window, 0, ym, xm, -ym);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_RIGHT:
|
||||
window_drag_resize(window, 0, 0, xm, 0);
|
||||
break;
|
||||
case MOUSE_STATE_RESIZE_BOTTOM_RIGHT:
|
||||
window_drag_resize(window, 0, 0, xm, ym);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Leave mouse state if the top window closes.
|
||||
// TODO: Leave mouse state if the top window is switched.
|
||||
}
|
||||
else if ( display->mouse_state != MOUSE_STATE_NONE )
|
||||
{
|
||||
display->mouse_state = MOUSE_STATE_NONE;
|
||||
display_schedule_redraw(display);
|
||||
}
|
||||
}
|
||||
|
||||
void display_on_resolution_change(struct display* display, size_t width,
|
||||
size_t height)
|
||||
{
|
||||
if ( display->screen_width == width && display->screen_height == height )
|
||||
return;
|
||||
display->screen_width = width;
|
||||
display->screen_height = height;
|
||||
display->pointer_x = width / 2;
|
||||
display->pointer_y = height / 2;
|
||||
for ( struct window* window = display->bottom_window;
|
||||
window;
|
||||
window = window->above_window )
|
||||
window_on_display_resolution_change(window, display);
|
||||
wallpaper(display->wallpaper);
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
.Dd June 11, 2023
|
||||
.Dt DISPLAY 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm display
|
||||
.Nd desktop environment
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Ar session ...
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a desktop environment and windowing system compositor.
|
||||
Applications talk to the
|
||||
.Nm
|
||||
server process to receive user input and show their graphical user interfaces
|
||||
in windows.
|
||||
.Pp
|
||||
The user's preferred startup applications are launched on startup by launching
|
||||
the
|
||||
.Xr session
|
||||
program (if set) or otherwise the
|
||||
.Xr displayrc 5
|
||||
script in the background.
|
||||
.Pp
|
||||
.Nm
|
||||
exits when Control + Alt + Delete is pressed.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width "12345678"
|
||||
.It Fl m Ar mouse
|
||||
Use
|
||||
.Pa mouse
|
||||
device instead of
|
||||
.Pa /dev/mouse .
|
||||
.It Fl t Ar tty
|
||||
Use
|
||||
.Pa tty
|
||||
device instead of
|
||||
.Pa /dev/tty .
|
||||
.It Fl s Ar socket
|
||||
Listen on
|
||||
.Pa socket
|
||||
instead of
|
||||
.Pa /var/run/display .
|
||||
.El
|
||||
.Pp
|
||||
The keyboard shortcuts are as follows:
|
||||
.Bl -tag -width "Control + Alt + Delete"
|
||||
.It Alt + F4
|
||||
Quit the current window.
|
||||
.It Alt + F10
|
||||
Maximize (or restore) the current window.
|
||||
.It Alt + Tab
|
||||
Switch to the next window.
|
||||
.It Alt + Drag
|
||||
Drag the current window.
|
||||
.It Control + Alt + Delete
|
||||
Exit the desktop environment.
|
||||
.It Control + Alt + T
|
||||
Launch the
|
||||
.Xr terminal 1
|
||||
application.
|
||||
.It Super + Left
|
||||
Tile the current window leftwards.
|
||||
.It Super + Right
|
||||
Tile the current window rightwards.
|
||||
.It Super + Up
|
||||
Tile the current window upwards.
|
||||
.It Super + Down
|
||||
Tile the current window downwards.
|
||||
.It F11
|
||||
Grab input for the current window.
|
||||
.It F12
|
||||
Release the input grab on the current window.
|
||||
.El
|
||||
.Pp
|
||||
The mouse gestures are as follow:
|
||||
.Bl -bullet
|
||||
.It
|
||||
Clicking on a window brings it to the foreground.
|
||||
.It
|
||||
Dragging the window title bar moves the window.
|
||||
.It
|
||||
Double clicking on the window title bar maximizes (or restores) the window.
|
||||
.It
|
||||
Clicking on the rectangle icon in the title bar maximizes (or restores) the
|
||||
window.
|
||||
.It
|
||||
Clicking on the X icon in the title bar closes the window.
|
||||
.It
|
||||
Dragging the edges of a window resizes it.
|
||||
.It
|
||||
Windows can be tiled by moving them when the cursor meets the left, right, top,
|
||||
and bottom edges or any corner.
|
||||
.El
|
||||
.Pp
|
||||
The keyboard layout can be changed with the
|
||||
.Xr chkblayout 1
|
||||
program.
|
||||
The display resolution can be changed with the
|
||||
.Xr chvideomode 1
|
||||
program.
|
||||
.Sh ENVIRONMENT
|
||||
.Bl -tag -width "DISPLAY_SOCKET"
|
||||
.It Ev DISPLAY_SOCKET
|
||||
.Nm
|
||||
sets
|
||||
.Ev DISPLAY_SOCKET
|
||||
to the path of the
|
||||
.Xr unix 4
|
||||
socket where it listens for connections from applications.
|
||||
Applications use
|
||||
.Ev DISPLAY_SOCKET
|
||||
to connect to
|
||||
.Nm
|
||||
or
|
||||
.Pa /var/run/display
|
||||
by default.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Bl -tag -width 12345678 -compact
|
||||
.It Pa ~/.displayrc , /etc/displayrc , /etc/default/displayrc
|
||||
.Xr displayrc 5
|
||||
script that spawns the user's preferred startup applications.
|
||||
.It Pa /var/run/display
|
||||
.Xr unix 4
|
||||
socket where
|
||||
.Nm
|
||||
listens for connections from applications, as advertised in the
|
||||
.Ev DISPLAY_SOCKET
|
||||
environment variable.
|
||||
.El
|
||||
.Sh ASYNCHRONOUS EVENTS
|
||||
.Bl -tag -width "SIGTERM"
|
||||
.It Dv SIGTERM
|
||||
Request daemon termination.
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Nm
|
||||
runs as a
|
||||
.Xr daemon 7
|
||||
until stopped by
|
||||
.Dv SIGTERM ,
|
||||
the user explicitly exits the desktop environment, or an application asks
|
||||
it to exit.
|
||||
.Nm
|
||||
signals readiness on the
|
||||
.Ev READYFD
|
||||
file descriptor when the display server is ready to receive connections from
|
||||
applications.
|
||||
.Nm
|
||||
will exit non-zero on any fatal startup error.
|
||||
.Sh EXAMPLES
|
||||
.Nm
|
||||
can be selected as the user's graphical user interface with this executable
|
||||
.Pa ~/.session
|
||||
script:
|
||||
.Bd -literal -offset indent
|
||||
#!/bin/sh
|
||||
exec display
|
||||
.Ed
|
||||
.Pp
|
||||
.Xr chkblayout 1 ,
|
||||
.Xr chvideomode 1 ,
|
||||
.Xr display 1
|
||||
will run the
|
||||
.Xr displayrc 5
|
||||
script on startup, which can be used to start applications.
|
||||
.Sh SEE ALSO
|
||||
.Xr terminal 1 ,
|
||||
.Xr displayrc 5 ,
|
||||
.Xr session 5
|
||||
.Sh BUGS
|
||||
The following features are not yet implemented:
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
Windows cannot be minimized.
|
||||
.It
|
||||
Applications cannot receive mouse events.
|
||||
.It
|
||||
The wallpaper is random and cannot be controlled.
|
||||
.El
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016, 2017, 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.
|
||||
*
|
||||
* display.c
|
||||
* Display server.
|
||||
*/
|
||||
|
||||
#include <err.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "arrow.inc"
|
||||
|
||||
#include "display.h"
|
||||
#include "framebuffer.h"
|
||||
#include "server.h"
|
||||
|
||||
uint32_t arrow_buffer[48 * 48];
|
||||
struct framebuffer arrow_framebuffer = { 48, arrow_buffer, 48, 48 };
|
||||
|
||||
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 main(int argc, char* argv[])
|
||||
{
|
||||
const char* mouse = "/dev/mouse";
|
||||
const char* socket = "/var/run/display";
|
||||
const char* tty = NULL;
|
||||
|
||||
int opt;
|
||||
while ( (opt = getopt(argc, argv, "m:s:t:")) != -1 )
|
||||
{
|
||||
switch ( opt )
|
||||
{
|
||||
case 'm': mouse = optarg; break;
|
||||
case 's': socket = optarg; break;
|
||||
case 't': tty = optarg; break;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(arrow_buffer, arrow, sizeof(arrow));
|
||||
|
||||
setlocale(LC_ALL, "");
|
||||
setvbuf(stdout, NULL, _IOLBF, 0);
|
||||
|
||||
if ( getpgid(0) != getpid() )
|
||||
errx(1, "This program must be run in its own process group");
|
||||
|
||||
struct display display;
|
||||
display_initialize(&display);
|
||||
|
||||
struct server server;
|
||||
server_initialize(&server, &display, tty, mouse, socket);
|
||||
|
||||
if ( setenv("DISPLAY_SOCKET", server.server_path, 1) < 0 )
|
||||
err(1, "setenv");
|
||||
|
||||
ready();
|
||||
|
||||
char* home_session = NULL;
|
||||
char** session_argv = NULL;
|
||||
if ( optind < argc )
|
||||
session_argv = argv + optind;
|
||||
else
|
||||
{
|
||||
const char* home = getenv("HOME");
|
||||
if ( home && asprintf(&home_session, "%s/.displayrc", home) < 0 )
|
||||
err(1, "malloc");
|
||||
const char* session_path = NULL;
|
||||
if ( !access(home_session, F_OK) )
|
||||
session_path = home_session;
|
||||
else if ( !access("/etc/displayrc", F_OK) )
|
||||
session_path = "/etc/displayrc";
|
||||
else if ( !access("/etc/default/displayrc", F_OK) )
|
||||
session_path = "/etc/default/displayrc";
|
||||
if ( session_path )
|
||||
session_argv = (char**) (const char*[]) {session_path, NULL};
|
||||
}
|
||||
|
||||
if ( session_argv )
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if ( pid < 0 )
|
||||
warn("fork");
|
||||
else if ( pid == 0 )
|
||||
{
|
||||
execvp(session_argv[0], session_argv);
|
||||
warn("%s", session_argv[0]);
|
||||
_exit(127);
|
||||
}
|
||||
}
|
||||
|
||||
free(home_session);
|
||||
|
||||
server_mainloop(&server);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* display.h
|
||||
* Display server.
|
||||
*/
|
||||
|
||||
#ifndef DISPLAY_H
|
||||
#define DISPLAY_H
|
||||
|
||||
#include <sys/display.h>
|
||||
#include <sys/ps2mouse.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "damage-rect.h"
|
||||
#include "framebuffer.h"
|
||||
|
||||
enum mouse_state
|
||||
{
|
||||
MOUSE_STATE_NONE,
|
||||
MOUSE_STATE_IGNORE,
|
||||
MOUSE_STATE_BUTTON_PRESS,
|
||||
MOUSE_STATE_TITLE_MOVE,
|
||||
MOUSE_STATE_RESIZE_BOTTOM,
|
||||
MOUSE_STATE_RESIZE_BOTTOM_LEFT,
|
||||
MOUSE_STATE_RESIZE_BOTTOM_RIGHT,
|
||||
MOUSE_STATE_RESIZE_LEFT,
|
||||
MOUSE_STATE_RESIZE_RIGHT,
|
||||
MOUSE_STATE_RESIZE_TOP,
|
||||
MOUSE_STATE_RESIZE_TOP_LEFT,
|
||||
MOUSE_STATE_RESIZE_TOP_RIGHT,
|
||||
};
|
||||
|
||||
struct window;
|
||||
|
||||
struct display
|
||||
{
|
||||
struct tiocgdisplay display;
|
||||
struct framebuffer fb;
|
||||
struct framebuffer wallpaper;
|
||||
size_t fb_size;
|
||||
size_t wallpaper_size;
|
||||
struct damage_rect damage_rect;
|
||||
struct window* top_window;
|
||||
struct window* bottom_window;
|
||||
struct window* active_window;
|
||||
struct window* tab_candidate;
|
||||
size_t screen_width;
|
||||
size_t screen_height;
|
||||
size_t num_tabs;
|
||||
bool key_lctrl;
|
||||
bool key_lalt;
|
||||
bool key_lsuper;
|
||||
bool key_rsuper;
|
||||
bool redraw;
|
||||
int pointer_x;
|
||||
int pointer_y;
|
||||
enum mouse_state mouse_state;
|
||||
size_t mouse_byte_count;
|
||||
uint8_t mouse_bytes[MOUSE_PACKET_SIZE];
|
||||
};
|
||||
|
||||
void display_initialize(struct display* display);
|
||||
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);
|
||||
void display_unlink_window(struct display* display, struct window* window);
|
||||
void display_unlink_window_removal(struct display* display,
|
||||
struct window* window);
|
||||
void display_unmark_active_window(struct display* display,
|
||||
struct window* window);
|
||||
void display_mark_active_window(struct display* display, struct window* window);
|
||||
void display_update_active_window(struct display* display);
|
||||
void display_move_window_to_top(struct display* display, struct window* window);
|
||||
void display_change_active_window(struct display* display,
|
||||
struct window* window);
|
||||
void display_set_active_window(struct display* display, struct window* window);
|
||||
void display_add_window(struct display* display, struct window* window);
|
||||
void display_remove_window(struct display* display, struct window* window);
|
||||
void display_composit(struct display* display, struct framebuffer fb);
|
||||
void display_schedule_redraw(struct display* display);
|
||||
void display_render(struct display* display);
|
||||
void display_keyboard_event(struct display* display, uint32_t codepoint);
|
||||
void display_on_resolution_change(struct display* display, size_t width,
|
||||
size_t height);
|
||||
void display_mouse_event(struct display* display, uint8_t byte);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
.Dd June 11, 2023
|
||||
.Dt DISPLAYRC 5
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm displayrc
|
||||
.Nd startup graphical applications
|
||||
.Sh SYNOPSIS
|
||||
.Nm ~/.displayrc
|
||||
.Nm /etc/displayrc
|
||||
.Nm /etc/default/displayrc
|
||||
.Sh DESCRIPTION
|
||||
.Xr display 1
|
||||
runs the
|
||||
.Nm
|
||||
script to launch the user's startup applications and prepare the desktop
|
||||
environment according to the user's preferences.
|
||||
.Pp
|
||||
.Xr display 1
|
||||
continues running after
|
||||
.Nm
|
||||
finishes running and any launched applications should be run as background
|
||||
processes.
|
||||
.Pp
|
||||
The
|
||||
.Nm
|
||||
script is found by searching for an executable script in the following paths:
|
||||
.Bl -bullet -compact
|
||||
.It
|
||||
.Pa ~/.displayrc
|
||||
.It
|
||||
.Pa /etc/displayrc
|
||||
.It
|
||||
.Pa /etc/default/displayrc
|
||||
.El
|
||||
.Pp
|
||||
.Nm
|
||||
is not executed if no script is found.
|
||||
.Sh ENVIRONMENT
|
||||
.Nm
|
||||
is executed with the following environment:
|
||||
.Bl -tag -width "DISPLAY_SOCKET"
|
||||
.It Ev DISPLAY_SOCKET
|
||||
The path of the
|
||||
.Xr unix 4 socket
|
||||
where
|
||||
.Xr display 1
|
||||
is listening for connections from applications.
|
||||
.El
|
||||
.Sh FILES
|
||||
.Bl -tag -width "/etc/default/displayrc" -compact
|
||||
.It Pa ~/.displayrc
|
||||
The user's
|
||||
.Nm
|
||||
script.
|
||||
.It Pa /etc/displayrc
|
||||
The system administor provided
|
||||
.Nm
|
||||
script.
|
||||
.It Pa /etc/default/displayrc
|
||||
The operating system provided
|
||||
.Nm
|
||||
script.
|
||||
.El
|
||||
.Sh EXAMPLES
|
||||
Launch a terminal with a text editor, another terminal with the user's default
|
||||
shell, and launch the asteroids game.
|
||||
.Bd -literal -offset indent
|
||||
terminal editor &
|
||||
terminal &
|
||||
asteroids &
|
||||
.Ed
|
||||
.Sh SEE ALSO
|
||||
.Xr display 1 ,
|
||||
.Xr terminal 1 ,
|
||||
.Xr profile 5 ,
|
||||
.Xr session 5 ,
|
||||
.Xr shrc 5
|
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* server.c
|
||||
* Display server main loop.
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/termmode.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <display-protocol.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "display.h"
|
||||
#include "server.h"
|
||||
#include "vgafont.h"
|
||||
|
||||
static int open_local_server_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 ( bind(fd, (const struct sockaddr*) sockaddr, addr_size) < 0 )
|
||||
return close(fd), free(sockaddr), -1;
|
||||
if ( listen(fd, SOMAXCONN) < 0 )
|
||||
return close(fd), free(sockaddr), -1;
|
||||
free(sockaddr);
|
||||
return fd;
|
||||
}
|
||||
|
||||
void server_initialize(struct server* server, struct display* display,
|
||||
const char* tty, const char* mouse, const char* socket)
|
||||
{
|
||||
memset(server, 0, sizeof(*server));
|
||||
|
||||
server->display = display;
|
||||
|
||||
load_font();
|
||||
|
||||
server->tty_fd = 0;
|
||||
if ( tty || !isatty(server->tty_fd) )
|
||||
{
|
||||
tty = tty ? tty : "/dev/tty";
|
||||
server->tty_fd = open(tty, O_RDONLY);
|
||||
if ( server->tty_fd < 0 )
|
||||
err(1, tty);
|
||||
}
|
||||
|
||||
// TODO: Support for multiple displays.
|
||||
struct tiocgdisplays gdisplays = {0};
|
||||
gdisplays.count = 1;
|
||||
gdisplays.displays = &display->display;
|
||||
if ( ioctl(server->tty_fd, TIOCGDISPLAYS, &gdisplays) < 0 ||
|
||||
gdisplays.count == 0 )
|
||||
errx(1, "%s: No video devices are associated with this terminal", tty);
|
||||
|
||||
server->mouse_fd = open(mouse, O_RDONLY | O_CLOEXEC);
|
||||
if ( server->mouse_fd < 0 )
|
||||
err(1, "%s", mouse);
|
||||
|
||||
server->server_path = socket;
|
||||
server->server_fd = open_local_server_socket(server->server_path,
|
||||
SOCK_NONBLOCK | SOCK_CLOEXEC);
|
||||
if ( server->server_fd < 0 )
|
||||
err(1, "open_local_server_socket: %s", server->server_path);
|
||||
|
||||
unsigned int termmode =
|
||||
TERMMODE_KBKEY | TERMMODE_UNICODE | TERMMODE_NONBLOCK;
|
||||
if ( settermmode(server->tty_fd, termmode) < 0 )
|
||||
err(1, "settermmode");
|
||||
|
||||
server->pfds_count = server_pfds_count(server);
|
||||
server->pfds =
|
||||
reallocarray(NULL, sizeof(struct pollfd), server->pfds_count);
|
||||
if ( !server->pfds )
|
||||
err(1, "malloc");
|
||||
}
|
||||
|
||||
bool server_accept(struct server* server)
|
||||
{
|
||||
int client_fd = accept4(server->server_fd, NULL, NULL, SOCK_NONBLOCK);
|
||||
if ( client_fd < 0 )
|
||||
{
|
||||
warn("accept: %s", server->server_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( server->connections_used == server->connections_length )
|
||||
{
|
||||
size_t new_length = server->connections_length * 2;
|
||||
if ( !new_length )
|
||||
new_length = 16;
|
||||
struct connection** new_connections =
|
||||
reallocarray(server->connections, new_length,
|
||||
sizeof(struct connection*));
|
||||
if ( !new_connections )
|
||||
{
|
||||
warn("dropped connection: %s: malloc", server->server_path);
|
||||
close(client_fd);
|
||||
return false;
|
||||
}
|
||||
server->connections = new_connections;
|
||||
server->connections_length = new_length;
|
||||
}
|
||||
|
||||
size_t new_pfds_count = server_pfds_count(server) + 1;
|
||||
struct pollfd* new_pfds =
|
||||
reallocarray(server->pfds, sizeof(struct pollfd), new_pfds_count);
|
||||
if ( !new_pfds )
|
||||
{
|
||||
warn("dropped connection: %s: malloc", server->server_path);
|
||||
close(client_fd);
|
||||
return false;
|
||||
}
|
||||
server->pfds = new_pfds;
|
||||
server->pfds_count = new_pfds_count;
|
||||
|
||||
struct connection* connection = malloc(sizeof(struct connection));
|
||||
if ( !connection )
|
||||
{
|
||||
warn("dropped connection: %s: malloc", server->server_path);
|
||||
close(client_fd);
|
||||
return false;
|
||||
}
|
||||
server->connections[server->connections_used++] = connection;
|
||||
connection_initialize(connection, server->display, client_fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t server_pfds_count(const struct server* server)
|
||||
{
|
||||
return 3 + server->connections_used;
|
||||
}
|
||||
|
||||
void server_poll(struct server* server)
|
||||
{
|
||||
int code;
|
||||
while ( 0 < waitpid(-1, &code, WNOHANG) )
|
||||
{
|
||||
}
|
||||
|
||||
struct pollfd* pfds = server->pfds;
|
||||
|
||||
pfds[0].fd = server->server_fd;
|
||||
pfds[0].events = POLLIN;
|
||||
pfds[0].revents = 0;
|
||||
pfds[1].fd = server->tty_fd;
|
||||
pfds[1].events = POLLIN;
|
||||
pfds[1].revents = 0;
|
||||
pfds[2].fd = server->mouse_fd;
|
||||
pfds[2].events = POLLIN;
|
||||
pfds[2].revents = 0;
|
||||
size_t cpfd_off = 3;
|
||||
|
||||
size_t connections_polled = server->connections_used;
|
||||
for ( size_t i = 0; i < connections_polled; i++ )
|
||||
{
|
||||
struct pollfd* pfd = &pfds[cpfd_off + i];
|
||||
struct connection* connection = server->connections[i];
|
||||
pfd->fd = connection->fd;
|
||||
pfd->events = connection_interested_poll_events(connection);
|
||||
pfd->revents = 0;
|
||||
}
|
||||
size_t pfds_used = cpfd_off + connections_polled;
|
||||
|
||||
int num_events = ppoll(pfds, pfds_used, NULL, NULL);
|
||||
if ( num_events < 0 )
|
||||
err(1, "poll");
|
||||
|
||||
if ( pfds[0].revents )
|
||||
{
|
||||
// TODO: Handle if this can actually happen.
|
||||
assert(!(pfds[0].revents & POLLERR));
|
||||
assert(!(pfds[0].revents & POLLHUP));
|
||||
assert(!(pfds[0].revents & POLLNVAL));
|
||||
|
||||
server_accept(server);
|
||||
}
|
||||
|
||||
if ( pfds[1].revents )
|
||||
{
|
||||
// TODO: Handle if this can actually happen.
|
||||
assert(!(pfds[1].revents & POLLERR));
|
||||
assert(!(pfds[1].revents & POLLHUP));
|
||||
assert(!(pfds[1].revents & POLLNVAL));
|
||||
|
||||
uint32_t codepoint;
|
||||
ssize_t size = sizeof(codepoint);
|
||||
while ( read(server->tty_fd, &codepoint, size) == size )
|
||||
display_keyboard_event(server->display, codepoint);
|
||||
}
|
||||
|
||||
if ( pfds[2].revents )
|
||||
{
|
||||
// TODO: Handle if this can actually happen.
|
||||
assert(!(pfds[2].revents & POLLERR));
|
||||
assert(!(pfds[2].revents & POLLHUP));
|
||||
assert(!(pfds[2].revents & POLLNVAL));
|
||||
|
||||
unsigned char events[64];
|
||||
ssize_t amount = read(server->mouse_fd, events, sizeof(events));
|
||||
for ( ssize_t i = 0; i < amount; i++ )
|
||||
display_mouse_event(server->display, events[i]);
|
||||
}
|
||||
|
||||
bool any_disconnect = false;
|
||||
for ( size_t i = 0; i < connections_polled; i++ )
|
||||
{
|
||||
struct pollfd* pfd = &pfds[cpfd_off + i];
|
||||
if ( !pfd->revents )
|
||||
continue;
|
||||
struct connection* connection = server->connections[i];
|
||||
if ( pfd->revents & (POLLERR | POLLHUP | POLLNVAL) &&
|
||||
!(pfd->revents & POLLIN) )
|
||||
{
|
||||
connection_destroy(connection);
|
||||
free(connection);
|
||||
server->connections[i] = NULL;
|
||||
any_disconnect = true;
|
||||
continue;
|
||||
}
|
||||
if ( pfd->revents & POLLOUT )
|
||||
connection_can_write(connection);
|
||||
if ( pfd->revents & POLLIN )
|
||||
connection_can_read(connection, server);
|
||||
}
|
||||
|
||||
// Compact the array down here so the pfds match the connections above.
|
||||
if ( any_disconnect )
|
||||
{
|
||||
size_t new_used = 0;
|
||||
for ( size_t i = 0; i < server->connections_used; i++ )
|
||||
{
|
||||
if ( server->connections[i] )
|
||||
server->connections[new_used++] = server->connections[i];
|
||||
}
|
||||
server->connections_used = new_used;
|
||||
}
|
||||
}
|
||||
|
||||
void server_mainloop(struct server* server)
|
||||
{
|
||||
while ( true )
|
||||
{
|
||||
display_render(server->display);
|
||||
server_poll(server);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 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.
|
||||
*
|
||||
* connection.h
|
||||
* Display server main loop.
|
||||
*/
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct connection;
|
||||
struct display;
|
||||
struct pollfd;
|
||||
|
||||
struct server
|
||||
{
|
||||
struct display* display;
|
||||
const char* server_path;
|
||||
int server_fd;
|
||||
int tty_fd;
|
||||
int mouse_fd;
|
||||
struct connection** connections;
|
||||
size_t connections_used;
|
||||
size_t connections_length;
|
||||
struct pollfd* pfds;
|
||||
size_t pfds_count;
|
||||
};
|
||||
|
||||
void server_initialize(struct server* server, struct display* display,
|
||||
const char* tty, const char* mouse, const char* socket);
|
||||
bool server_accept(struct server* server);
|
||||
size_t server_pfds_count(const struct server* server);
|
||||
void server_poll(struct server* server);
|
||||
void server_mainloop(struct server* server);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,554 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016, 2017, 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.
|
||||
*
|
||||
* window.c
|
||||
* Window abstraction.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <timespec.h>
|
||||
|
||||
#include <display-protocol.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "display.h"
|
||||
#include "framebuffer.h"
|
||||
#include "pixel.h"
|
||||
#include "vgafont.h"
|
||||
#include "window.h"
|
||||
|
||||
struct framebuffer window_client_buffer(struct window* window)
|
||||
{
|
||||
if ( window->window_state != WINDOW_STATE_REGULAR )
|
||||
return framebuffer_crop(window->buffer, 0, TITLE_HEIGHT,
|
||||
window->width, window->height - TITLE_HEIGHT);
|
||||
return framebuffer_crop(window->buffer, BORDER_WIDTH, TITLE_HEIGHT,
|
||||
window->width - 2 * BORDER_WIDTH,
|
||||
window->height - TITLE_HEIGHT - BORDER_WIDTH);
|
||||
}
|
||||
|
||||
void window_schedule_redraw(struct window* window)
|
||||
{
|
||||
if ( window->show )
|
||||
display_schedule_redraw(window->display);
|
||||
}
|
||||
|
||||
void window_render_frame(struct window* window)
|
||||
{
|
||||
if ( !window->width || !window->height )
|
||||
return;
|
||||
|
||||
bool has_focus = window->display->tab_candidate ?
|
||||
window->display->tab_candidate == window :
|
||||
window->display->active_window == window;
|
||||
|
||||
uint32_t glass_color = has_focus ? make_color_a(200, 200, 255, 192)
|
||||
: make_color_a(180, 180, 255, 128);
|
||||
uint32_t title_color = has_focus ? make_color_a(16, 16, 16, 240)
|
||||
: make_color_a(32, 32, 32, 200);
|
||||
uint32_t button_hover_glass = make_color_a(220, 220, 255, 255);
|
||||
uint32_t button_press_glass = make_color_a(180, 180, 255, 255);
|
||||
|
||||
size_t start_x = 0;
|
||||
size_t start_y = 0;
|
||||
size_t end_x = window->width - 1;
|
||||
size_t end_y = window->height - 1;
|
||||
|
||||
bool maximized = window->window_state != WINDOW_STATE_REGULAR;
|
||||
|
||||
int b0 = 0;
|
||||
int b1 = 1;
|
||||
int b2 = 2;
|
||||
int b3 = BORDER_WIDTH;
|
||||
int t0 = TITLE_HEIGHT;
|
||||
|
||||
for ( size_t y = start_y; y <= end_y; y++ )
|
||||
{
|
||||
for ( size_t x = start_x; x <= end_x; x++ )
|
||||
{
|
||||
uint32_t color;
|
||||
if ( maximized && y < start_y + t0 )
|
||||
color = glass_color;
|
||||
else if ( maximized )
|
||||
continue;
|
||||
else if ( x == start_x + b0 || x == end_x - b0 ||
|
||||
y == start_y + b0 || y == end_y - b0 )
|
||||
color = make_color_a(0, 0, 0, 32);
|
||||
else if ( x == start_x + b1 || x == end_x - b1 ||
|
||||
y == start_y + b1 || y == end_y - b1 )
|
||||
color = make_color_a(0, 0, 0, 64);
|
||||
else if ( x == start_x + b2 || x == end_x - b2 ||
|
||||
y == start_y + b2 || y == end_y - b2 )
|
||||
color = make_color(240, 240, 250);
|
||||
else if ( x < start_x + (b3-1) || x > end_x - (b3-1) ||
|
||||
y < start_y + (t0-1) || y > end_y - (b3-1) )
|
||||
color = glass_color;
|
||||
else if ( x == start_x + (b3-1) || x == end_x - (b3-1) ||
|
||||
y == start_y + (t0-1) || y == end_y - (b3-1) )
|
||||
color = make_color(64, 64, 64);
|
||||
else
|
||||
continue;
|
||||
framebuffer_set_pixel(window->buffer, x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
const char* tt = window->title ? window->title : "";
|
||||
ssize_t tt_width = render_text_width(tt); // Potentially adjusted later.
|
||||
size_t tt_height = FONT_HEIGHT;
|
||||
size_t tt_pos_y = (TITLE_HEIGHT - FONT_HEIGHT) / 2 + 2;
|
||||
uint32_t tt_color = title_color;
|
||||
|
||||
size_t border_width = maximized ? 0 : b2 + 1;
|
||||
size_t button_area_height = maximized ? t0 : t0 - (b2 + 1);
|
||||
size_t button_area_width = button_area_height;
|
||||
size_t button_area_top = maximized ? 0 : b2;
|
||||
size_t button_size = FONT_WIDTH - 1;
|
||||
size_t button_top = (button_area_height - button_size + 1) / 2;
|
||||
size_t button_left = (button_area_width - button_size + 1) / 2;
|
||||
ssize_t buttons_x = window->width - border_width - button_area_width*3 + 1;
|
||||
struct framebuffer buttons_fb =
|
||||
framebuffer_crop(window->buffer, buttons_x, button_area_top,
|
||||
button_area_width * 3, button_area_height);
|
||||
for ( size_t n = 0; n < 3; n++ )
|
||||
{
|
||||
uint32_t color = glass_color;
|
||||
switch ( window->button_states[n] )
|
||||
{
|
||||
case BUTTON_STATE_NORMAL: continue;
|
||||
case BUTTON_STATE_HOVER: color = button_hover_glass; break;
|
||||
case BUTTON_STATE_PRESSED: color = button_press_glass; break;
|
||||
}
|
||||
size_t bx = button_area_width * n;
|
||||
size_t by = 0;
|
||||
for ( size_t y = 0; y < button_area_height; y++ )
|
||||
for ( size_t x = 0; x < button_area_width; x++ )
|
||||
framebuffer_set_pixel(buttons_fb, bx + x, by + y, color);
|
||||
}
|
||||
for ( size_t i = 0; i < button_size; i++ )
|
||||
{
|
||||
size_t bx = button_area_width * 0 + button_left;
|
||||
size_t by = button_top;
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + button_size - 1, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + button_size - 2, tt_color);
|
||||
}
|
||||
for ( size_t i = 0; i < button_size; i++ )
|
||||
{
|
||||
size_t bx = button_area_width * 1 + button_left;
|
||||
size_t by = button_top;
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + button_size - 1 , tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx,
|
||||
by + i, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + button_size - 1,
|
||||
by + i, tt_color);
|
||||
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + 1, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + button_size - 2 , tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + 1,
|
||||
by + i, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + button_size - 2,
|
||||
by + i, tt_color);
|
||||
}
|
||||
for ( size_t i = 0; i < button_size; i++ )
|
||||
{
|
||||
size_t bx = button_area_width * 2 + button_left;
|
||||
size_t by = button_top;
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + i, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + i,
|
||||
by + button_size - 1 - i, tt_color);
|
||||
|
||||
framebuffer_set_pixel(buttons_fb, bx + i - 1,
|
||||
by + i, tt_color);
|
||||
framebuffer_set_pixel(buttons_fb, bx + i - 1,
|
||||
by + button_size - 1 - i, tt_color);
|
||||
}
|
||||
|
||||
ssize_t q = 500 - window->width;
|
||||
ssize_t q_width = 200;
|
||||
q = q < q_width ? q : q_width;
|
||||
q = 0 < q ? q : 0;
|
||||
ssize_t center_over = window->width - (button_area_width * 3 * q / q_width);
|
||||
ssize_t tt_pos_x = (center_over - tt_width) / 2;
|
||||
if ( tt_pos_x < (ssize_t)border_width )
|
||||
{
|
||||
tt_pos_x = border_width;
|
||||
tt_width = buttons_x - border_width;
|
||||
tt_width = 0 < tt_width ? tt_width : 0;
|
||||
}
|
||||
render_text(framebuffer_crop(window->buffer, tt_pos_x, tt_pos_y,
|
||||
tt_width, tt_height), tt, tt_color);
|
||||
window_schedule_redraw(window);
|
||||
}
|
||||
|
||||
void window_move(struct window* window, size_t left, size_t top)
|
||||
{
|
||||
window->left = left;
|
||||
window->top = top;
|
||||
window_schedule_redraw(window);
|
||||
}
|
||||
|
||||
void window_client_resize(struct window* window,
|
||||
size_t client_width,
|
||||
size_t client_height)
|
||||
{
|
||||
if ( window->window_state != WINDOW_STATE_MINIMIZED )
|
||||
window->window_state = WINDOW_STATE_REGULAR;
|
||||
|
||||
struct framebuffer old_fb = window->buffer;
|
||||
|
||||
window->width = client_width + BORDER_WIDTH + BORDER_WIDTH;
|
||||
window->height = client_height + TITLE_HEIGHT + BORDER_WIDTH;
|
||||
|
||||
window->buffer.xres = window->width;
|
||||
window->buffer.yres = window->height;
|
||||
window->buffer.pitch = window->width;
|
||||
// TODO: Check malloc.
|
||||
window->buffer.buffer =
|
||||
malloc(sizeof(uint32_t) * window->width * window->height);
|
||||
for ( size_t y = 0; y < window->height; y++ )
|
||||
for ( size_t x = 0; x < window->width; x++ )
|
||||
framebuffer_set_pixel(window->buffer, x, y,
|
||||
framebuffer_get_pixel(old_fb, x, y));
|
||||
|
||||
free(old_fb.buffer);
|
||||
|
||||
window_render_frame(window);
|
||||
window_notify_client_resize(window);
|
||||
window_schedule_redraw(window);
|
||||
}
|
||||
|
||||
void window_resize(struct window* window, size_t width, size_t height)
|
||||
{
|
||||
if ( width < BORDER_WIDTH + BORDER_WIDTH )
|
||||
width = BORDER_WIDTH + BORDER_WIDTH;
|
||||
if ( height < TITLE_HEIGHT + BORDER_WIDTH )
|
||||
height = TITLE_HEIGHT + BORDER_WIDTH;
|
||||
// TODO: Keep proper track of this for each state.
|
||||
size_t client_width = width - (BORDER_WIDTH + BORDER_WIDTH);
|
||||
size_t client_height = height - (TITLE_HEIGHT + BORDER_WIDTH);
|
||||
window_client_resize(window, client_width, client_height);
|
||||
}
|
||||
|
||||
void window_drag_resize(struct window* window, int ld, int td, int wd, int hd)
|
||||
{
|
||||
// TODO: Keep proper track of this for each state.
|
||||
size_t client_width = window->width - (BORDER_WIDTH + BORDER_WIDTH);
|
||||
size_t client_height = window->height - (TITLE_HEIGHT + BORDER_WIDTH);
|
||||
if ( ld || td )
|
||||
window_move(window, window->left + ld, window->top + td);
|
||||
if ( wd || hd )
|
||||
{
|
||||
ssize_t new_width = (ssize_t) client_width + wd;
|
||||
ssize_t new_height = (ssize_t) client_height + hd;
|
||||
if ( new_width < 1 )
|
||||
new_width = 1;
|
||||
if ( new_height < 1 )
|
||||
new_height = 1;
|
||||
window_client_resize(window, new_width, new_height);
|
||||
}
|
||||
}
|
||||
|
||||
static size_t next_window_position = 25;
|
||||
|
||||
void window_initialize(struct window* window,
|
||||
struct connection* connection,
|
||||
struct display* display,
|
||||
uint32_t window_id)
|
||||
{
|
||||
memset(window, 0, sizeof(*window));
|
||||
window->created = true;
|
||||
window->connection = connection;
|
||||
window->display = display;
|
||||
window->title_click_time = timespec_make(-1, 0);
|
||||
window->window_id = window_id;
|
||||
display_add_window(window->display, window);
|
||||
window->top = next_window_position;
|
||||
window->left = next_window_position;
|
||||
size_t max_position = display->screen_width < display->screen_height ?
|
||||
display->screen_width : display->screen_height;
|
||||
max_position = (max_position * 6) / 10;
|
||||
next_window_position += 30;
|
||||
next_window_position %= max_position;
|
||||
window_client_resize(window, 0, 0);
|
||||
}
|
||||
|
||||
void window_quit(struct window* window)
|
||||
{
|
||||
struct event_quit event;
|
||||
event.window_id = window->window_id;
|
||||
|
||||
struct display_packet_header header;
|
||||
header.id = EVENT_QUIT;
|
||||
header.size = sizeof(event);
|
||||
|
||||
assert(window->connection);
|
||||
|
||||
connection_schedule_transmit(window->connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(window->connection, &event, sizeof(event));
|
||||
}
|
||||
|
||||
void window_destroy(struct window* window)
|
||||
{
|
||||
display_remove_window(window->display, window);
|
||||
free(window->buffer.buffer);
|
||||
free(window->title);
|
||||
memset(window, 0, sizeof(*window));
|
||||
window->created = false;
|
||||
}
|
||||
|
||||
void window_on_display_resolution_change(struct window* window,
|
||||
struct display* display)
|
||||
{
|
||||
// TODO: Move window back inside screen.
|
||||
if ( window->window_state == WINDOW_STATE_MAXIMIZED )
|
||||
{
|
||||
// TODO: Change size of maximized window.
|
||||
(void) display;
|
||||
}
|
||||
}
|
||||
|
||||
void window_tile(struct window* window, enum window_state state, size_t left,
|
||||
size_t top, size_t width, size_t height)
|
||||
{
|
||||
if ( window->window_state == state )
|
||||
return;
|
||||
|
||||
if ( window->window_state == WINDOW_STATE_REGULAR )
|
||||
{
|
||||
window->saved_left = window->left;
|
||||
window->saved_top = window->top;
|
||||
window->saved_width = window->width;
|
||||
window->saved_height = window->height;
|
||||
}
|
||||
|
||||
free(window->buffer.buffer);
|
||||
|
||||
window->left = left;
|
||||
window->top = top;
|
||||
window->width = width;
|
||||
window->height = height;
|
||||
|
||||
// TODO: Share logic with window_client_resize.
|
||||
window->buffer.xres = window->width;
|
||||
window->buffer.yres = window->height;
|
||||
window->buffer.pitch = window->width;
|
||||
// TODO: Check malloc.
|
||||
window->buffer.buffer =
|
||||
calloc(1, sizeof(uint32_t) * window->width * window->height);
|
||||
|
||||
window->window_state = state;
|
||||
|
||||
window_render_frame(window);
|
||||
window_notify_client_resize(window);
|
||||
}
|
||||
|
||||
void window_maximize(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_MAXIMIZED,
|
||||
0, 0,
|
||||
window->display->screen_width, window->display->screen_height);
|
||||
}
|
||||
|
||||
void window_restore(struct window* window)
|
||||
{
|
||||
if ( window->window_state == WINDOW_STATE_REGULAR )
|
||||
return;
|
||||
window->top = window->saved_top;
|
||||
window->left = window->saved_left;
|
||||
window_client_resize(window, window->saved_width - 2 * BORDER_WIDTH,
|
||||
window->saved_height - TITLE_HEIGHT - BORDER_WIDTH);
|
||||
window_notify_client_resize(window);
|
||||
}
|
||||
|
||||
void window_toggle_maximized(struct window* window)
|
||||
{
|
||||
if ( window->window_state == WINDOW_STATE_MAXIMIZED )
|
||||
window_restore(window);
|
||||
else
|
||||
window_maximize(window);
|
||||
}
|
||||
|
||||
void window_tile_leftward(struct window* window)
|
||||
{
|
||||
switch ( window->window_state )
|
||||
{
|
||||
case WINDOW_STATE_REGULAR: window_tile_left(window); break;
|
||||
case WINDOW_STATE_MAXIMIZED: window_tile_left(window); break;
|
||||
case WINDOW_STATE_MINIMIZED: window_tile_right(window); break;
|
||||
case WINDOW_STATE_TILE_LEFT: break;
|
||||
case WINDOW_STATE_TILE_RIGHT: window_restore(window); break;
|
||||
case WINDOW_STATE_TILE_TOP: window_tile_top_left(window); break;
|
||||
case WINDOW_STATE_TILE_TOP_LEFT: break;
|
||||
case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_top(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_left(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_LEFT: break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_bottom(window); break;
|
||||
}
|
||||
}
|
||||
|
||||
void window_tile_rightward(struct window* window)
|
||||
{
|
||||
switch ( window->window_state )
|
||||
{
|
||||
case WINDOW_STATE_REGULAR: window_tile_right(window); break;
|
||||
case WINDOW_STATE_MAXIMIZED: window_tile_right(window); break;
|
||||
case WINDOW_STATE_MINIMIZED: window_tile_right(window); break;
|
||||
case WINDOW_STATE_TILE_LEFT: window_restore(window); break;
|
||||
case WINDOW_STATE_TILE_RIGHT: break;
|
||||
case WINDOW_STATE_TILE_TOP: window_tile_top_right(window); break;
|
||||
case WINDOW_STATE_TILE_TOP_LEFT: window_tile_top(window); break;
|
||||
case WINDOW_STATE_TILE_TOP_RIGHT: break;
|
||||
case WINDOW_STATE_TILE_BOTTOM: window_tile_bottom_right(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_bottom(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_RIGHT: break;
|
||||
}
|
||||
}
|
||||
|
||||
void window_tile_up(struct window* window)
|
||||
{
|
||||
switch ( window->window_state )
|
||||
{
|
||||
case WINDOW_STATE_REGULAR: window_tile_top(window); break;
|
||||
case WINDOW_STATE_MAXIMIZED: window_restore(window); break;
|
||||
case WINDOW_STATE_MINIMIZED: window_tile_top(window); break;
|
||||
case WINDOW_STATE_TILE_LEFT: window_tile_top_left(window); break;
|
||||
case WINDOW_STATE_TILE_RIGHT: window_tile_top_right(window); break;
|
||||
case WINDOW_STATE_TILE_TOP: window_maximize(window); break;
|
||||
case WINDOW_STATE_TILE_TOP_LEFT: break;
|
||||
case WINDOW_STATE_TILE_TOP_RIGHT: break;
|
||||
case WINDOW_STATE_TILE_BOTTOM: window_restore(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_LEFT: window_tile_left(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_RIGHT: window_tile_right(window); break;
|
||||
}
|
||||
}
|
||||
|
||||
void window_tile_down(struct window* window)
|
||||
{
|
||||
switch ( window->window_state )
|
||||
{
|
||||
case WINDOW_STATE_REGULAR: window_tile_bottom(window); break;
|
||||
case WINDOW_STATE_MAXIMIZED: window_tile_top(window); break;
|
||||
case WINDOW_STATE_MINIMIZED: window_tile_bottom(window); break;
|
||||
case WINDOW_STATE_TILE_LEFT: window_tile_bottom_left(window); break;
|
||||
case WINDOW_STATE_TILE_RIGHT: window_tile_bottom_right(window); break;
|
||||
case WINDOW_STATE_TILE_TOP: window_restore(window); break;
|
||||
case WINDOW_STATE_TILE_TOP_LEFT: window_tile_left(window); break;
|
||||
case WINDOW_STATE_TILE_TOP_RIGHT: window_tile_right(window); break;
|
||||
case WINDOW_STATE_TILE_BOTTOM: break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_LEFT: break;
|
||||
case WINDOW_STATE_TILE_BOTTOM_RIGHT: break;
|
||||
}
|
||||
}
|
||||
|
||||
void window_tile_left(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_LEFT,
|
||||
0,
|
||||
0,
|
||||
window->display->screen_width / 2,
|
||||
window->display->screen_height);
|
||||
}
|
||||
|
||||
void window_tile_right(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_RIGHT,
|
||||
(window->display->screen_width + 1) / 2,
|
||||
0,
|
||||
(window->display->screen_width + 1) / 2,
|
||||
window->display->screen_height);
|
||||
}
|
||||
|
||||
void window_tile_top(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_TOP,
|
||||
0,
|
||||
0,
|
||||
window->display->screen_width,
|
||||
window->display->screen_height / 2);
|
||||
}
|
||||
|
||||
void window_tile_top_left(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_TOP_LEFT,
|
||||
0,
|
||||
0,
|
||||
window->display->screen_width / 2,
|
||||
window->display->screen_height / 2);
|
||||
}
|
||||
|
||||
void window_tile_top_right(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_TOP_RIGHT,
|
||||
(window->display->screen_width + 1) / 2,
|
||||
0,
|
||||
(window->display->screen_width + 1) / 2,
|
||||
window->display->screen_height / 2);
|
||||
}
|
||||
|
||||
void window_tile_bottom(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_BOTTOM,
|
||||
0,
|
||||
(window->display->screen_height + 1) / 2,
|
||||
window->display->screen_width,
|
||||
(window->display->screen_height + 1) / 2);
|
||||
}
|
||||
|
||||
void window_tile_bottom_left(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_BOTTOM_LEFT,
|
||||
0,
|
||||
(window->display->screen_height + 1) / 2,
|
||||
window->display->screen_width / 2,
|
||||
(window->display->screen_height + 1) / 2);
|
||||
}
|
||||
|
||||
void window_tile_bottom_right(struct window* window)
|
||||
{
|
||||
window_tile(window, WINDOW_STATE_TILE_BOTTOM_RIGHT,
|
||||
(window->display->screen_width + 1) / 2,
|
||||
(window->display->screen_height + 1) / 2,
|
||||
(window->display->screen_width + 1) / 2,
|
||||
(window->display->screen_height + 1) / 2);
|
||||
}
|
||||
|
||||
void window_notify_client_resize(struct window* window)
|
||||
{
|
||||
struct event_resize event;
|
||||
event.window_id = window->window_id;
|
||||
event.width = window_client_buffer(window).xres;
|
||||
event.height = window_client_buffer(window).yres;
|
||||
|
||||
struct display_packet_header header;
|
||||
header.id = EVENT_RESIZE;
|
||||
header.size = sizeof(event);
|
||||
|
||||
assert(window->connection);
|
||||
|
||||
connection_schedule_transmit(window->connection, &header, sizeof(header));
|
||||
connection_schedule_transmit(window->connection, &event, sizeof(event));
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016, 2018, 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.
|
||||
*
|
||||
* window.h
|
||||
* Window abstraction.
|
||||
*/
|
||||
|
||||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "framebuffer.h"
|
||||
|
||||
struct connection;
|
||||
struct display;
|
||||
|
||||
static const size_t BORDER_WIDTH = 8;
|
||||
static const size_t TITLE_HEIGHT = 28;
|
||||
static const size_t RESIZE_GRACE = 16;
|
||||
|
||||
enum button_state
|
||||
{
|
||||
BUTTON_STATE_NORMAL,
|
||||
BUTTON_STATE_HOVER,
|
||||
BUTTON_STATE_PRESSED,
|
||||
};
|
||||
|
||||
enum window_state
|
||||
{
|
||||
WINDOW_STATE_REGULAR,
|
||||
WINDOW_STATE_MAXIMIZED,
|
||||
WINDOW_STATE_MINIMIZED,
|
||||
WINDOW_STATE_TILE_LEFT,
|
||||
WINDOW_STATE_TILE_RIGHT,
|
||||
WINDOW_STATE_TILE_TOP,
|
||||
WINDOW_STATE_TILE_TOP_LEFT,
|
||||
WINDOW_STATE_TILE_TOP_RIGHT,
|
||||
WINDOW_STATE_TILE_BOTTOM,
|
||||
WINDOW_STATE_TILE_BOTTOM_LEFT,
|
||||
WINDOW_STATE_TILE_BOTTOM_RIGHT,
|
||||
};
|
||||
|
||||
struct window
|
||||
{
|
||||
struct display* display;
|
||||
struct connection* connection;
|
||||
struct window* above_window;
|
||||
struct window* below_window;
|
||||
struct framebuffer buffer;
|
||||
struct timespec title_click_time;
|
||||
char* title;
|
||||
ssize_t left;
|
||||
ssize_t top;
|
||||
size_t width;
|
||||
size_t height;
|
||||
ssize_t saved_left;
|
||||
ssize_t saved_top;
|
||||
size_t saved_width;
|
||||
size_t saved_height;
|
||||
uint32_t window_id;
|
||||
enum window_state window_state;
|
||||
enum button_state button_states[3];
|
||||
bool created;
|
||||
bool show;
|
||||
bool focus;
|
||||
bool grab_input;
|
||||
};
|
||||
|
||||
struct framebuffer window_client_buffer(struct window* window);
|
||||
void window_schedule_redraw(struct window* window);
|
||||
void window_render_frame(struct window* window);
|
||||
void window_move(struct window* window, size_t left, size_t top);
|
||||
void window_resize(struct window* window, size_t width, size_t height);
|
||||
void window_client_resize(struct window* window, size_t client_width,
|
||||
size_t client_height);
|
||||
void window_initialize(struct window* window, struct connection* connection,
|
||||
struct display* display, uint32_t window_id);
|
||||
void window_quit(struct window* window);
|
||||
void window_destroy(struct window* window);
|
||||
void window_drag_resize(struct window* window, int ld, int td, int wd, int hd);
|
||||
void window_on_display_resolution_change(struct window* window,
|
||||
struct display* display);
|
||||
void window_maximize(struct window* window);
|
||||
void window_restore(struct window* window);
|
||||
void window_toggle_maximized(struct window* window);
|
||||
void window_tile(struct window* window, enum window_state state, size_t left,
|
||||
size_t top, size_t width, size_t height);
|
||||
void window_tile_leftward(struct window* window);
|
||||
void window_tile_rightward(struct window* window);
|
||||
void window_tile_up(struct window* window);
|
||||
void window_tile_down(struct window* window);
|
||||
void window_tile_left(struct window* window);
|
||||
void window_tile_right(struct window* window);
|
||||
void window_tile_top(struct window* window);
|
||||
void window_tile_top_left(struct window* window);
|
||||
void window_tile_top_right(struct window* window);
|
||||
void window_tile_bottom(struct window* window);
|
||||
void window_tile_bottom_left(struct window* window);
|
||||
void window_tile_bottom_right(struct window* window);
|
||||
void window_notify_client_resize(struct window* window);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,2 @@
|
|||
untrusted comment: signify public key
|
||||
RWQiTQbFzyZJVobf/pn53Jp3njhRB9DgwkMaNakCpDE9RaTABMjlbz9W
|
|
@ -0,0 +1,2 @@
|
|||
untrusted comment: signify public key
|
||||
RWQnkSm9lj1YIZYpt1Y3mHYzFsaky82gQF6CrW4lme9OoEYzSIl2ZsIC
|
|
@ -0,0 +1,2 @@
|
|||
untrusted comment: signify public key
|
||||
RWTGrBXmGvl2zUpCa47ui5EyPsnitKLjsCZ2YZphNY8F3b33t6QWYDs1
|
|
@ -0,0 +1,2 @@
|
|||
untrusted comment: signify public key
|
||||
RWRTbLQ+3+a9I5yche2BEVP03TRtumGO4Vgq1AQ/5bRj8JAJ1R0+vpxE
|
|
@ -15,7 +15,7 @@ BINARIES:=\
|
|||
asteroids \
|
||||
aquatinspitz \
|
||||
|
||||
LIBS:=-ldispd
|
||||
LIBS:=-ldisplay
|
||||
|
||||
all: $(BINARIES)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2014, 2015, 2016 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 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
|
||||
|
@ -22,6 +22,7 @@
|
|||
#include <sys/types.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
#include <math.h>
|
||||
|
@ -34,12 +35,15 @@
|
|||
#include <timespec.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dispd.h>
|
||||
#include <display.h>
|
||||
|
||||
// Utility global variables every game will need.
|
||||
uint32_t window_id = 0;
|
||||
static size_t framesize;
|
||||
static uint32_t* fb;
|
||||
static bool game_running = true;
|
||||
static size_t game_width = 1280;
|
||||
static size_t game_height = 720;
|
||||
static size_t game_width = 800;
|
||||
static size_t game_height = 512;
|
||||
#define MAX_KEY_NUMBER 512
|
||||
static bool keys_down[MAX_KEY_NUMBER];
|
||||
static bool keys_pending[MAX_KEY_NUMBER];
|
||||
|
@ -200,20 +204,16 @@ void update(float deltatime)
|
|||
}
|
||||
|
||||
// Render the game into the framebuffer.
|
||||
void render(struct dispd_window* window)
|
||||
void render(struct display_connection* connection)
|
||||
{
|
||||
struct dispd_framebuffer* window_fb = dispd_begin_render(window);
|
||||
if ( !window_fb )
|
||||
{
|
||||
error(0, 0, "unable to begin rendering dispd window");
|
||||
game_running = false;
|
||||
return;
|
||||
}
|
||||
size_t old_framesize = framesize;
|
||||
|
||||
uint32_t* fb = (uint32_t*) dispd_get_framebuffer_data(window_fb);
|
||||
size_t xres = dispd_get_framebuffer_width(window_fb);
|
||||
size_t yres = dispd_get_framebuffer_height(window_fb);
|
||||
size_t pitch = dispd_get_framebuffer_pitch(window_fb) / sizeof(uint32_t);
|
||||
size_t xres = game_width;
|
||||
size_t yres = game_height;
|
||||
size_t pitch = xres;
|
||||
framesize = xres * yres * sizeof(uint32_t);
|
||||
if ( old_framesize != framesize && !(fb = realloc(fb, framesize)) )
|
||||
err(1, "malloc");
|
||||
|
||||
// Render a colorful background.
|
||||
for ( size_t y = 0; y < yres; y++ )
|
||||
|
@ -273,7 +273,9 @@ void render(struct dispd_window* window)
|
|||
}
|
||||
}
|
||||
|
||||
dispd_finish_render(window_fb);
|
||||
display_render_window(connection, window_id, 0, 0,
|
||||
game_width, game_height, fb);
|
||||
display_show_window(connection, window_id);
|
||||
}
|
||||
|
||||
// ... to here. No need to edit stuff below.
|
||||
|
@ -310,50 +312,68 @@ bool pop_is_key_just_down(int abskbkey)
|
|||
return true;
|
||||
}
|
||||
|
||||
// Read input from the keyboard.
|
||||
void input(void)
|
||||
// When the connection to the display server has disconnected.
|
||||
void on_disconnect(void* ctx)
|
||||
{
|
||||
// Read the keyboard input from the user.
|
||||
unsigned termmode = TERMMODE_KBKEY | TERMMODE_SIGNAL | TERMMODE_NONBLOCK;
|
||||
if ( settermmode(0, termmode) )
|
||||
error(1, errno, "settermmode");
|
||||
uint32_t codepoint;
|
||||
ssize_t numbytes;
|
||||
while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) )
|
||||
{
|
||||
int kbkey = KBKEY_DECODE(codepoint);
|
||||
if( !kbkey )
|
||||
continue;
|
||||
int abskbkey = (kbkey < 0) ? -kbkey : kbkey;
|
||||
if ( MAX_KEY_NUMBER <= (size_t) abskbkey )
|
||||
continue;
|
||||
bool is_key_down_event = 0 < kbkey;
|
||||
if ( !keys_down[abskbkey] && is_key_down_event )
|
||||
keys_pending[abskbkey] = true;
|
||||
keys_down[abskbkey] = is_key_down_event;
|
||||
}
|
||||
(void) ctx;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// When the window is asked to quit.
|
||||
void on_quit(void* ctx, uint32_t window_id)
|
||||
{
|
||||
(void) ctx;
|
||||
(void) window_id;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// When the window has been resized.
|
||||
void on_resize(void* ctx, uint32_t window_id, uint32_t width, uint32_t height)
|
||||
{
|
||||
(void) ctx;
|
||||
if ( window_id != window_id )
|
||||
return;
|
||||
game_width = width;
|
||||
game_height = height;
|
||||
}
|
||||
|
||||
// When a key has been pressed.
|
||||
void on_keyboard(void* ctx, uint32_t window_id, uint32_t codepoint)
|
||||
{
|
||||
(void) ctx;
|
||||
if ( window_id != window_id )
|
||||
return;
|
||||
int kbkey = KBKEY_DECODE(codepoint);
|
||||
if ( !kbkey )
|
||||
return;
|
||||
int abskbkey = (kbkey < 0) ? -kbkey : kbkey;
|
||||
if ( MAX_KEY_NUMBER <= (size_t) abskbkey )
|
||||
return;
|
||||
bool is_key_down_event = 0 < kbkey;
|
||||
if ( !keys_down[abskbkey] && is_key_down_event )
|
||||
keys_pending[abskbkey] = true;
|
||||
keys_down[abskbkey] = is_key_down_event;
|
||||
}
|
||||
|
||||
// Run the game until no longer needed.
|
||||
void mainloop(struct dispd_window* window)
|
||||
void mainloop(struct display_connection* connection)
|
||||
{
|
||||
struct dispd_framebuffer* window_fb = dispd_begin_render(window);
|
||||
if ( window_fb )
|
||||
{
|
||||
game_width = dispd_get_framebuffer_width(window_fb);
|
||||
game_height = dispd_get_framebuffer_height(window_fb);
|
||||
dispd_finish_render(window_fb);
|
||||
}
|
||||
struct display_event_handlers handlers = {0};
|
||||
handlers.disconnect_handler = on_disconnect;
|
||||
handlers.quit_handler = on_quit;
|
||||
handlers.resize_handler = on_resize;
|
||||
handlers.keyboard_handler = on_keyboard;
|
||||
|
||||
init();
|
||||
|
||||
struct timespec last_frame_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, &last_frame_time);
|
||||
|
||||
render(window);
|
||||
render(connection);
|
||||
|
||||
while ( game_running )
|
||||
{
|
||||
|
||||
struct timespec current_frame_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, ¤t_frame_time);
|
||||
|
||||
|
@ -361,71 +381,31 @@ void mainloop(struct dispd_window* window)
|
|||
timespec_sub(current_frame_time, last_frame_time);
|
||||
float deltatime = deltatime_ts.tv_sec + deltatime_ts.tv_nsec / 1E9f;
|
||||
|
||||
input();
|
||||
while ( display_poll_event(connection, &handlers) == 0 );
|
||||
|
||||
update(deltatime);
|
||||
render(window);
|
||||
render(connection);
|
||||
|
||||
last_frame_time = current_frame_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the terminal state when the process terminates.
|
||||
static struct termios saved_tio;
|
||||
|
||||
static void restore_terminal_on_exit(void)
|
||||
{
|
||||
tcsetattr(0, TCSAFLUSH, &saved_tio);
|
||||
}
|
||||
|
||||
static void restore_terminal_on_signal(int signum)
|
||||
{
|
||||
if ( signum == SIGTSTP )
|
||||
{
|
||||
struct termios tio;
|
||||
tcgetattr(0, &tio);
|
||||
tcsetattr(0, TCSAFLUSH, &saved_tio);
|
||||
raise(SIGSTOP);
|
||||
tcgetattr(0, &saved_tio);
|
||||
tcsetattr(0, TCSAFLUSH, &tio);
|
||||
return;
|
||||
}
|
||||
tcsetattr(0, TCSAFLUSH, &saved_tio);
|
||||
raise(signum);
|
||||
}
|
||||
|
||||
// Create a display context, run the game, and then cleanly exit.
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if ( !isatty(0) )
|
||||
error(1, errno, "standard input");
|
||||
if ( tcgetattr(0, &saved_tio) < 0 )
|
||||
error(1, errno, "tcsetattr: standard input");
|
||||
if ( atexit(restore_terminal_on_exit) != 0 )
|
||||
error(1, errno, "atexit");
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = restore_terminal_on_signal;
|
||||
sigaction(SIGTSTP, &sa, NULL);
|
||||
sa.sa_flags = SA_RESETHAND;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGQUIT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
struct display_connection* connection = display_connect_default();
|
||||
if ( !connection && errno == ECONNREFUSED )
|
||||
display_spawn(argc, argv);
|
||||
if ( !connection )
|
||||
error(1, errno, "Could not connect to display server");
|
||||
|
||||
if ( !dispd_initialize(&argc, &argv) )
|
||||
error(1, 0, "couldn't initialize dispd library");
|
||||
struct dispd_session* session = dispd_attach_default_session();
|
||||
if ( !session )
|
||||
error(1, 0, "couldn't attach to dispd default session");
|
||||
if ( !dispd_session_setup_game_rgba(session) )
|
||||
error(1, 0, "couldn't setup dispd rgba session");
|
||||
struct dispd_window* window = dispd_create_window_game_rgba(session);
|
||||
if ( !window )
|
||||
error(1, 0, "couldn't create dispd rgba window");
|
||||
display_create_window(connection, window_id);
|
||||
display_resize_window(connection, window_id, game_width, game_height);
|
||||
display_title_window(connection, window_id, "Aquatinspitz");
|
||||
|
||||
mainloop(window);
|
||||
mainloop(connection);
|
||||
|
||||
dispd_destroy_window(window);
|
||||
dispd_detach_session(session);
|
||||
display_disconnect(connection);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <sys/termmode.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <error.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -36,7 +37,11 @@
|
|||
#include <timespec.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <dispd.h>
|
||||
#include <display.h>
|
||||
|
||||
uint32_t WINDOW_ID = 0;
|
||||
uint32_t WINDOW_WIDTH = 800;
|
||||
uint32_t WINDOW_HEIGHT = 512;
|
||||
|
||||
static inline float RandomFloat()
|
||||
{
|
||||
|
@ -60,7 +65,7 @@ static inline float RandomAngle()
|
|||
|
||||
static inline uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b)
|
||||
{
|
||||
return b << 0UL | g << 8UL | r << 16UL;
|
||||
return b << 0UL | g << 8UL | r << 16UL | 0xFF << 24UL;
|
||||
}
|
||||
|
||||
static const size_t STARFIELD_WIDTH = 512UL;
|
||||
|
@ -110,31 +115,6 @@ bool pop_is_key_just_down(int abskbkey)
|
|||
return true;
|
||||
}
|
||||
|
||||
void FetchKeyboardInput()
|
||||
{
|
||||
// Read the keyboard input from the user.
|
||||
const unsigned termmode = TERMMODE_KBKEY
|
||||
| TERMMODE_SIGNAL
|
||||
| TERMMODE_NONBLOCK;
|
||||
if ( settermmode(0, termmode) )
|
||||
error(1, errno, "settermmode");
|
||||
uint32_t codepoint;
|
||||
ssize_t numbytes;
|
||||
while ( 0 < (numbytes = read(0, &codepoint, sizeof(codepoint))) )
|
||||
{
|
||||
int kbkey = KBKEY_DECODE(codepoint);
|
||||
if( !kbkey )
|
||||
continue;
|
||||
int abskbkey = (kbkey < 0) ? -kbkey : kbkey;
|
||||
if ( MAXKEYNUM <= (size_t) abskbkey )
|
||||
continue;
|
||||
bool is_key_down_event = 0 < kbkey;
|
||||
if ( !keysdown[abskbkey] && is_key_down_event )
|
||||
keyspending[abskbkey] = true;
|
||||
keysdown[abskbkey] = is_key_down_event;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t xres;
|
||||
static size_t yres;
|
||||
static size_t bpp;
|
||||
|
@ -1143,25 +1123,88 @@ void Render()
|
|||
obj->Render();
|
||||
}
|
||||
}
|
||||
void RunFrame(struct dispd_window* window)
|
||||
|
||||
void on_disconnect(void*)
|
||||
{
|
||||
struct dispd_framebuffer* fb = dispd_begin_render(window);
|
||||
if ( !fb )
|
||||
{
|
||||
error(0, 0, "unable to begin rendering dispd window");
|
||||
gamerunning = false;
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void on_quit(void*, uint32_t)
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void on_resize(void*, uint32_t window_id, uint32_t width, uint32_t height)
|
||||
{
|
||||
if ( window_id != WINDOW_ID )
|
||||
return;
|
||||
WINDOW_WIDTH = width;
|
||||
WINDOW_HEIGHT = height;
|
||||
}
|
||||
|
||||
void on_keyboard(void*, uint32_t window_id, uint32_t codepoint)
|
||||
{
|
||||
if ( window_id != WINDOW_ID )
|
||||
return;
|
||||
int kbkey = KBKEY_DECODE(codepoint);
|
||||
if( !kbkey )
|
||||
return;
|
||||
int abskbkey = (kbkey < 0) ? -kbkey : kbkey;
|
||||
if ( MAXKEYNUM <= (size_t) abskbkey )
|
||||
return;
|
||||
bool is_key_down_event = 0 < kbkey;
|
||||
if ( !keysdown[abskbkey] && is_key_down_event )
|
||||
keyspending[abskbkey] = true;
|
||||
keysdown[abskbkey] = is_key_down_event;
|
||||
}
|
||||
|
||||
void RunFrame(struct display_connection* connection)
|
||||
{
|
||||
struct display_event_handlers handlers;
|
||||
memset(&handlers, 0, sizeof(handlers));
|
||||
handlers.disconnect_handler = on_disconnect;
|
||||
handlers.quit_handler = on_quit;
|
||||
handlers.resize_handler = on_resize;
|
||||
handlers.keyboard_handler = on_keyboard;
|
||||
|
||||
while ( display_poll_event(connection, &handlers) == 0 );
|
||||
|
||||
size_t old_framesize = framesize;
|
||||
|
||||
xres = WINDOW_WIDTH;
|
||||
yres = WINDOW_HEIGHT;
|
||||
bpp = 32;
|
||||
linesize = WINDOW_WIDTH;
|
||||
framesize = WINDOW_WIDTH * sizeof(uint32_t) * WINDOW_HEIGHT;
|
||||
if ( old_framesize != framesize )
|
||||
{
|
||||
free(buf);
|
||||
buf = (uint32_t*) malloc(framesize);
|
||||
if ( !buf )
|
||||
err(1, "malloc");
|
||||
}
|
||||
xres = dispd_get_framebuffer_width(fb);
|
||||
yres = dispd_get_framebuffer_height(fb);
|
||||
bpp = dispd_get_framebuffer_format(fb);
|
||||
linesize = dispd_get_framebuffer_pitch(fb) / (bpp / 8);
|
||||
framesize = dispd_get_framebuffer_pitch(fb) * yres;
|
||||
buf = (uint32_t*) dispd_get_framebuffer_data(fb);
|
||||
FetchKeyboardInput();
|
||||
|
||||
GameLogic();
|
||||
Render();
|
||||
dispd_finish_render(fb);
|
||||
|
||||
static int fps_counter = 0;
|
||||
fps_counter++;
|
||||
static time_t last_frame_sec = 0;
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
if ( now.tv_sec != last_frame_sec )
|
||||
{
|
||||
char* title = NULL;
|
||||
asprintf(&title, "Asteroids (fps %i)", fps_counter);
|
||||
display_title_window(connection, WINDOW_ID, title);
|
||||
free(title);
|
||||
fps_counter = 0;
|
||||
last_frame_sec = now.tv_sec;
|
||||
}
|
||||
|
||||
display_render_window(connection, WINDOW_ID, 0, 0,
|
||||
WINDOW_WIDTH, WINDOW_HEIGHT, buf);
|
||||
display_show_window(connection, WINDOW_ID);
|
||||
}
|
||||
|
||||
void InitGame()
|
||||
|
@ -1172,64 +1215,24 @@ void InitGame()
|
|||
new AsteroidField;
|
||||
}
|
||||
|
||||
static struct termios saved_tio;
|
||||
|
||||
static void restore_terminal_on_exit(void)
|
||||
{
|
||||
tcsetattr(0, TCSAFLUSH, &saved_tio);
|
||||
}
|
||||
|
||||
static void restore_terminal_on_signal(int signum)
|
||||
{
|
||||
if ( signum == SIGTSTP )
|
||||
{
|
||||
struct termios tio;
|
||||
tcgetattr(0, &tio);
|
||||
tcsetattr(0, TCSAFLUSH, &saved_tio);
|
||||
raise(SIGSTOP);
|
||||
tcgetattr(0, &saved_tio);
|
||||
tcsetattr(0, TCSAFLUSH, &tio);
|
||||
return;
|
||||
}
|
||||
tcsetattr(0, TCSAFLUSH, &saved_tio);
|
||||
raise(signum);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if ( !isatty(0) )
|
||||
error(1, errno, "standard input");
|
||||
if ( tcgetattr(0, &saved_tio) < 0 )
|
||||
error(1, errno, "tcsetattr: standard input");
|
||||
if ( atexit(restore_terminal_on_exit) != 0 )
|
||||
error(1, errno, "atexit");
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = restore_terminal_on_signal;
|
||||
sigaction(SIGTSTP, &sa, NULL);
|
||||
sa.sa_flags = SA_RESETHAND;
|
||||
sigaction(SIGINT, &sa, NULL);
|
||||
sigaction(SIGQUIT, &sa, NULL);
|
||||
sigaction(SIGTERM, &sa, NULL);
|
||||
struct display_connection* connection = display_connect_default();
|
||||
if ( !connection && errno == ECONNREFUSED )
|
||||
display_spawn(argc, argv);
|
||||
if ( !connection )
|
||||
error(1, errno, "Could not connect to display server");
|
||||
|
||||
if ( !dispd_initialize(&argc, &argv) )
|
||||
error(1, 0, "couldn't initialize dispd library");
|
||||
struct dispd_session* session = dispd_attach_default_session();
|
||||
if ( !session )
|
||||
error(1, 0, "couldn't attach to dispd default session");
|
||||
if ( !dispd_session_setup_game_rgba(session) )
|
||||
error(1, 0, "couldn't setup dispd rgba session");
|
||||
struct dispd_window* window = dispd_create_window_game_rgba(session);
|
||||
if ( !window )
|
||||
error(1, 0, "couldn't create dispd rgba window");
|
||||
display_create_window(connection, WINDOW_ID);
|
||||
display_resize_window(connection, WINDOW_ID, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
display_title_window(connection, WINDOW_ID, "Asteroids");
|
||||
|
||||
InitGame();
|
||||
gamerunning = true;
|
||||
for ( framenum = 0; gamerunning; framenum++ )
|
||||
RunFrame(window);
|
||||
RunFrame(connection);
|
||||
|
||||
dispd_destroy_window(window);
|
||||
dispd_detach_session(session);
|
||||
display_disconnect(connection);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
host
|
|
@ -0,0 +1,25 @@
|
|||
include ../build-aux/platform.mak
|
||||
include ../build-aux/compiler.mak
|
||||
include ../build-aux/version.mak
|
||||
include ../build-aux/dirs.mak
|
||||
|
||||
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
|
||||
CFLAGS?=$(OPTLEVEL)
|
||||
|
||||
CFLAGS += -Wall -Wextra
|
||||
|
||||
BINARIES = host
|
||||
|
||||
all: $(BINARIES)
|
||||
|
||||
.PHONY: all install clean
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(BINDIR)
|
||||
install $(BINARIES) $(DESTDIR)$(BINDIR)
|
||||
|
||||
%: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(BINARIES)
|
|
@ -0,0 +1,511 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* host.c
|
||||
* Domain name system client.
|
||||
*/
|
||||
|
||||
#include <sys/dnsconfig.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <endian.h>
|
||||
#include <err.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DNS_SIZE 512
|
||||
#define DNS_NAME_MAX 255
|
||||
#define DNS_LABEL_MAX 64
|
||||
|
||||
struct dns_header
|
||||
{
|
||||
uint16_t id;
|
||||
uint16_t flags;
|
||||
uint16_t qdcount;
|
||||
uint16_t ancount;
|
||||
uint16_t nscount;
|
||||
uint16_t arcount;
|
||||
};
|
||||
|
||||
struct dns_question
|
||||
{
|
||||
uint16_t qtype;
|
||||
uint16_t qclass;
|
||||
};
|
||||
|
||||
struct dns_record
|
||||
{
|
||||
uint16_t type;
|
||||
uint16_t class;
|
||||
uint16_t ttl_high;
|
||||
uint16_t ttl_low;
|
||||
uint16_t rdlength;
|
||||
};
|
||||
|
||||
#define DNS_HEADER_FLAGS_RCODE_MASK (0xF << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_NO (0 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_FORMAT (1 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_SERVER (2 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_NAME (3 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_NOT_IMPLEMENTED (4 << 0)
|
||||
#define DNS_HEADER_FLAGS_RCODE_REFUSED (5 << 0)
|
||||
#define DNS_HEADER_FLAGS_RA (1 << 7)
|
||||
#define DNS_HEADER_FLAGS_RD (1 << 8)
|
||||
#define DNS_HEADER_FLAGS_TC (1 << 9)
|
||||
#define DNS_HEADER_FLAGS_AA (1 << 10)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_MASK (0xF << 11)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_QUERY (0 << 11)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_IQUERY (1 << 11)
|
||||
#define DNS_HEADER_FLAGS_OPCODE_STATUS (2 << 11)
|
||||
#define DNS_HEADER_FLAGS_QR (1 << 15)
|
||||
|
||||
#define DNS_TYPE_A 1
|
||||
#define DNS_TYPE_NS 2
|
||||
#define DNS_TYPE_MD 3
|
||||
#define DNS_TYPE_MF 4
|
||||
#define DNS_TYPE_CNAME 5
|
||||
#define DNS_TYPE_SOA 6
|
||||
#define DNS_TYPE_MB 7
|
||||
#define DNS_TYPE_MG 8
|
||||
#define DNS_TYPE_MR 9
|
||||
#define DNS_TYPE_NULL 10
|
||||
#define DNS_TYPE_WKS 11
|
||||
#define DNS_TYPE_PTR 12
|
||||
#define DNS_TYPE_HINFO 13
|
||||
#define DNS_TYPE_MINFO 14
|
||||
#define DNS_TYPE_MX 15
|
||||
#define DNS_TYPE_TXT 16
|
||||
#define DNS_TYPE_AAAA 28
|
||||
|
||||
#define DNS_QTYPE_AXFR 252
|
||||
#define DNS_QTYPE_MAILB 253
|
||||
#define DNS_QTYPE_MAILA 254
|
||||
#define DNS_QTYPE_ANY 255
|
||||
|
||||
#define DNS_CLASS_IN 1
|
||||
#define DNS_CLASS_CS 2
|
||||
#define DNS_CLASS_CH 3
|
||||
#define DNS_CLASS_HS 4
|
||||
|
||||
#define DNS_QCLASS_ANY 255
|
||||
|
||||
static size_t encode_dns_header(unsigned char* msg,
|
||||
size_t offset,
|
||||
const struct dns_header* hdrin)
|
||||
{
|
||||
struct dns_header hdr;
|
||||
if ( DNS_SIZE - offset < sizeof(hdr) )
|
||||
errx(1, "dns message too large");
|
||||
hdr.id = htobe16(hdrin->id);
|
||||
hdr.flags = htobe16(hdrin->flags);
|
||||
hdr.qdcount = htobe16(hdrin->qdcount);
|
||||
hdr.ancount = htobe16(hdrin->ancount);
|
||||
hdr.nscount = htobe16(hdrin->nscount);
|
||||
hdr.arcount = htobe16(hdrin->arcount);
|
||||
memcpy(msg + offset, &hdr, sizeof(hdr));
|
||||
return offset + sizeof(hdr);
|
||||
}
|
||||
|
||||
static size_t encode_dns_byte(unsigned char* msg,
|
||||
size_t offset,
|
||||
unsigned char byte)
|
||||
{
|
||||
if ( DNS_SIZE - offset < 1 )
|
||||
errx(1, "dns message too large");
|
||||
msg[offset] = byte;
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
static size_t encode_dns_name(unsigned char* msg,
|
||||
size_t offset,
|
||||
const char* name)
|
||||
{
|
||||
size_t index = 0;
|
||||
size_t namelen = 0;
|
||||
if ( !name[0] )
|
||||
errx(1, "'%s' is not a valid name (unexpected end of input)", name);
|
||||
while ( name[index] )
|
||||
{
|
||||
if ( !strcmp(name + index, ".") )
|
||||
break;
|
||||
if ( name[index] == '.' )
|
||||
errx(1, "'%s' is not a valid name (empty label)", name);
|
||||
size_t length = strcspn(name + index, ".");
|
||||
if ( DNS_LABEL_MAX <= length )
|
||||
errx(1, "'%s' is not a valid name (label too long)", name);
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
errx(1, "'%s' is not a valid name (name is too long)", name);
|
||||
offset = encode_dns_byte(msg, offset, length & 0xFF);
|
||||
for ( size_t i = 0; i < length; i++ )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
errx(1, "'%s' is not a valid name (name is too long)", name);
|
||||
offset = encode_dns_byte(msg, offset, name[index + i]);
|
||||
}
|
||||
index += length;
|
||||
if ( name[index] == '.' )
|
||||
index++;
|
||||
}
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
errx(1, "'%s' is not a valid name (name is too long)", name);
|
||||
offset = encode_dns_byte(msg, offset, 0);
|
||||
return offset;
|
||||
}
|
||||
|
||||
static size_t encode_dns_question(unsigned char* msg,
|
||||
size_t offset,
|
||||
const char* name,
|
||||
const struct dns_question* qsin)
|
||||
{
|
||||
offset = encode_dns_name(msg, offset, name);
|
||||
struct dns_question qs;
|
||||
if ( DNS_SIZE - offset < sizeof(qs) )
|
||||
errx(1, "dns message too large");
|
||||
qs.qtype = htobe16(qsin->qtype);
|
||||
qs.qclass = htobe16(qsin->qclass);
|
||||
memcpy(msg + offset, &qs, sizeof(qs));
|
||||
return offset + sizeof(qs);
|
||||
}
|
||||
|
||||
static size_t decode_dns_header(const unsigned char* msg,
|
||||
size_t offset,
|
||||
size_t msg_size,
|
||||
struct dns_header* hdrout)
|
||||
{
|
||||
struct dns_header hdr;
|
||||
if ( msg_size - offset < sizeof(hdr) )
|
||||
errx(1, "dns message too small");
|
||||
memcpy(&hdr, msg + offset, sizeof(hdr));
|
||||
hdrout->id = be16toh(hdr.id);
|
||||
hdrout->flags = be16toh(hdr.flags);
|
||||
hdrout->qdcount = be16toh(hdr.qdcount);
|
||||
hdrout->ancount = be16toh(hdr.ancount);
|
||||
hdrout->nscount = be16toh(hdr.nscount);
|
||||
hdrout->arcount = be16toh(hdr.arcount);
|
||||
return offset + sizeof(hdr);
|
||||
}
|
||||
|
||||
static size_t decode_dns_byte(const unsigned char* msg,
|
||||
size_t offset,
|
||||
size_t msg_size,
|
||||
unsigned char* byte)
|
||||
{
|
||||
if ( msg_size <= offset || msg_size - offset < 1 )
|
||||
errx(1, "dns message too small");
|
||||
*byte = msg[offset];
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
static size_t decode_dns_name(const unsigned char* msg,
|
||||
size_t offset,
|
||||
size_t msg_size,
|
||||
char* name)
|
||||
{
|
||||
bool real_offset_set = false;
|
||||
size_t real_offset = 0;
|
||||
size_t index = 0;
|
||||
size_t namelen = 0;
|
||||
uint8_t b;
|
||||
while ( true )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
errx(1, "name too long");
|
||||
offset = decode_dns_byte(msg, offset, msg_size, &b);
|
||||
if ( 0xC0 & b )
|
||||
{
|
||||
namelen--;
|
||||
size_t ptr = (b & 0x3F) << 8;
|
||||
offset = decode_dns_byte(msg, offset, msg_size, &b);
|
||||
ptr |= b;
|
||||
if ( !real_offset_set )
|
||||
{
|
||||
real_offset = offset;
|
||||
real_offset_set = true;
|
||||
}
|
||||
offset = ptr;
|
||||
continue;
|
||||
}
|
||||
size_t length = b;
|
||||
if ( DNS_LABEL_MAX <= length )
|
||||
errx(1, "label too long");
|
||||
if ( !length )
|
||||
break;
|
||||
if ( index )
|
||||
name[index++] = '.';
|
||||
for ( size_t i = 0; i < length; i++ )
|
||||
{
|
||||
if ( namelen++ == DNS_NAME_MAX )
|
||||
errx(1, "name too long");
|
||||
offset = decode_dns_byte(msg, offset, msg_size, &b);
|
||||
// TODO: Handle if b == '.'.
|
||||
name[index++] = b;
|
||||
}
|
||||
}
|
||||
name[index++] = '.';
|
||||
name[index] = '\0';
|
||||
if ( real_offset_set )
|
||||
return real_offset;
|
||||
return offset;
|
||||
}
|
||||
|
||||
static size_t decode_dns_question(const unsigned char* msg,
|
||||
size_t offset,
|
||||
size_t msg_size,
|
||||
char* name,
|
||||
struct dns_question* qsout)
|
||||
{
|
||||
offset = decode_dns_name(msg, offset, msg_size, name);
|
||||
struct dns_question qs;
|
||||
if ( msg_size <= offset || msg_size - offset < sizeof(qs) )
|
||||
errx(1, "dns message too small");
|
||||
memcpy(&qs, msg + offset, sizeof(qs));
|
||||
qsout->qtype = be16toh(qs.qtype);
|
||||
qsout->qclass = be16toh(qs.qclass);
|
||||
return offset + sizeof(qs);
|
||||
}
|
||||
|
||||
static size_t decode_dns_record(const unsigned char* msg,
|
||||
size_t offset,
|
||||
size_t msg_size,
|
||||
char* name,
|
||||
struct dns_record* rrout)
|
||||
{
|
||||
offset = decode_dns_name(msg, offset, msg_size, name);
|
||||
struct dns_record rr;
|
||||
if ( msg_size <= offset || msg_size - offset < sizeof(rr) )
|
||||
errx(1, "dns message too small");
|
||||
memcpy(&rr, msg + offset, sizeof(rr));
|
||||
rrout->type = be16toh(rr.type);
|
||||
rrout->class = be16toh(rr.class);
|
||||
rrout->ttl_high = be16toh(rr.ttl_high);
|
||||
rrout->ttl_low = be16toh(rr.ttl_low);
|
||||
rrout->rdlength = be16toh(rr.rdlength);
|
||||
return offset + sizeof(rr);
|
||||
}
|
||||
|
||||
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)--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int ipv = 4;
|
||||
|
||||
const char* argv0 = argv[0];
|
||||
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] != '-' )
|
||||
{
|
||||
char c;
|
||||
while ( (c = *++arg) ) switch ( c )
|
||||
{
|
||||
case '4': ipv = 4; break;
|
||||
case '6': ipv = 6; break;
|
||||
default:
|
||||
fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: unknown option: %s\n", argv0, arg);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
compact_arguments(&argc, &argv);
|
||||
|
||||
if ( argc < 2 )
|
||||
errx(1, "No host given");
|
||||
const char* host = argv[1];
|
||||
|
||||
char nsipstr[INET_ADDRSTRLEN];
|
||||
const char* nameserver;
|
||||
if ( argc < 3 )
|
||||
{
|
||||
struct dnsconfig dnscfg;
|
||||
if ( getdnsconfig(&dnscfg) < 0 )
|
||||
err(1, "dnsconfig");
|
||||
bool found = false;
|
||||
for ( size_t i = 0; !found && i < dnscfg.servers_count; i++ )
|
||||
{
|
||||
if ( dnscfg.servers[i].family != AF_INET )
|
||||
continue;
|
||||
inet_ntop(AF_INET, &dnscfg.servers[i].addr, nsipstr, sizeof(nsipstr));
|
||||
found = true;
|
||||
}
|
||||
if ( !found )
|
||||
errx(1, "No nameserver given and no default configured");
|
||||
nameserver = nsipstr;
|
||||
}
|
||||
else
|
||||
nameserver = argv[2];
|
||||
|
||||
int port = 4 <= argc ? atoi(argv[3]) : 53;
|
||||
if ( 5 <= argc )
|
||||
errx(1, "Unexpected extra operand");
|
||||
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if ( fd < 0 )
|
||||
err(1, "socket");
|
||||
|
||||
struct sockaddr_in addr;
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htobe16(port);
|
||||
if ( inet_pton(AF_INET, nameserver, &addr.sin_addr) < 1 )
|
||||
errx(1, "invalid ip address: %s", nameserver);
|
||||
|
||||
if ( connect(fd, (const struct sockaddr*) &addr, sizeof(addr)) < 0 )
|
||||
err(1, "connect");
|
||||
|
||||
unsigned char req[DNS_SIZE];
|
||||
size_t req_size = 0;
|
||||
struct dns_header hdr;
|
||||
hdr.id = 0;
|
||||
hdr.flags = DNS_HEADER_FLAGS_RD;
|
||||
hdr.qdcount = 1;
|
||||
hdr.ancount = 0;
|
||||
hdr.nscount = 0;
|
||||
hdr.arcount = 0;
|
||||
req_size = encode_dns_header(req, req_size, &hdr);
|
||||
struct dns_question qs;
|
||||
qs.qtype = ipv == 4 ? DNS_TYPE_A : DNS_TYPE_AAAA;
|
||||
qs.qclass = DNS_CLASS_IN;
|
||||
req_size = encode_dns_question(req, req_size, host, &qs);
|
||||
|
||||
ssize_t amount = send(fd, req, req_size, 0);
|
||||
if ( amount < 0 )
|
||||
err(1, "send");
|
||||
|
||||
// TODO: Use recvfrom to get the server ip.
|
||||
unsigned char resp[DNS_SIZE];
|
||||
ssize_t resp_size = recv(fd, resp, sizeof(resp), 0);
|
||||
if ( resp_size < 0 )
|
||||
err(1, "recv");
|
||||
|
||||
// TODO: Verify the response came from the correct ip.
|
||||
|
||||
size_t offset = 0;
|
||||
offset = decode_dns_header(resp, offset, resp_size, &hdr);
|
||||
|
||||
// TODO: Verify the response has the correct id.
|
||||
|
||||
#if 0
|
||||
printf("id = %u\n", hdr.id);
|
||||
printf("flags = 0x%X\n", hdr.flags);
|
||||
printf("qdcount = %u\n", hdr.qdcount);
|
||||
printf("ancount = %u\n", hdr.ancount);
|
||||
printf("nscount = %u\n", hdr.nscount);
|
||||
printf("arcount = %u\n", hdr.arcount);
|
||||
#endif
|
||||
|
||||
uint16_t rcode = hdr.flags & DNS_HEADER_FLAGS_RCODE_MASK;
|
||||
if ( rcode == DNS_HEADER_FLAGS_RCODE_FORMAT )
|
||||
errx(1, "format error");
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_SERVER )
|
||||
errx(1, "server error");
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_NAME )
|
||||
errx(1, "no such name");
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_NOT_IMPLEMENTED )
|
||||
errx(1, "not implemented error");
|
||||
else if ( rcode == DNS_HEADER_FLAGS_RCODE_REFUSED )
|
||||
errx(1, "refused");
|
||||
else if ( rcode != DNS_HEADER_FLAGS_RCODE_NO )
|
||||
errx(1, "unknown error (rcode=0x%X)", rcode);
|
||||
|
||||
if ( hdr.flags & DNS_HEADER_FLAGS_TC )
|
||||
errx(1, "truncated");
|
||||
|
||||
// TODO: Check query bit.
|
||||
|
||||
for ( uint16_t i = 0; i < hdr.qdcount; i++ )
|
||||
{
|
||||
char name[DNS_NAME_MAX + 1];
|
||||
offset = decode_dns_question(resp, offset, resp_size, name, &qs);
|
||||
//printf("%s type=%u class=%u\n", name, qs.qtype, qs.qclass);
|
||||
}
|
||||
|
||||
for ( uint16_t i = 0; i < hdr.ancount; i++ )
|
||||
{
|
||||
char name[DNS_NAME_MAX + 1];
|
||||
struct dns_record rr;
|
||||
offset = decode_dns_record(resp, offset, resp_size, name, &rr);
|
||||
uint32_t ttl = (uint32_t) rr.ttl_high << 16 | rr.ttl_low;
|
||||
printf("%s type=%u class=%u ttl=%u ", name, rr.type, rr.class, ttl);
|
||||
if ( rr.class == DNS_CLASS_IN && rr.type == DNS_TYPE_A )
|
||||
{
|
||||
unsigned char ip[4];
|
||||
for ( size_t i = 0; i < 4; i++ )
|
||||
offset = decode_dns_byte(resp, offset, resp_size, &ip[i]);
|
||||
printf("%u.%u.%u.%u", ip[0], ip[1], ip[2], ip[3]);
|
||||
}
|
||||
else if ( rr.class == DNS_CLASS_IN && rr.type == DNS_TYPE_AAAA )
|
||||
{
|
||||
unsigned char ip[16];
|
||||
for ( size_t i = 0; i < 16; i++ )
|
||||
offset = decode_dns_byte(resp, offset, resp_size, &ip[i]);
|
||||
for ( size_t i = 0; i < 16; i++ )
|
||||
{
|
||||
if ( i && !(i & 1) )
|
||||
putchar(':');
|
||||
printf("%02x", ip[i]);
|
||||
}
|
||||
}
|
||||
else if ( rr.type == DNS_TYPE_CNAME )
|
||||
{
|
||||
char cname[DNS_NAME_MAX + 1];
|
||||
offset = decode_dns_name(resp, offset, resp_size, cname);
|
||||
printf("CNAME %s", cname);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("0x");
|
||||
fflush(stdout);
|
||||
for ( size_t i = 0; i < rr.rdlength; i++ )
|
||||
{
|
||||
unsigned char b;
|
||||
offset = decode_dns_byte(resp, offset, resp_size, &b);
|
||||
if ( isprint(b) && b != '\'' )
|
||||
printf("'%c'", b);
|
||||
else
|
||||
printf("%02X", b);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
irc
|
||||
*.o
|
||||
config.h
|
||||
tests
|
|
@ -0,0 +1,56 @@
|
|||
include ../build-aux/platform.mak
|
||||
include ../build-aux/compiler.mak
|
||||
include ../build-aux/version.mak
|
||||
include ../build-aux/dirs.mak
|
||||
|
||||
OPTLEVEL?=$(DEFAULT_OPTLEVEL)
|
||||
CFLAGS?=$(OPTLEVEL)
|
||||
|
||||
CFLAGS += -Wall -Wextra
|
||||
CPPFLAGS += -DVERSIONSTR=\"$(VERSION)\"
|
||||
|
||||
ifeq ($(HOST_IS_SORTIX),0)
|
||||
CPPFLAGS+=-D_GNU_SOURCE
|
||||
endif
|
||||
|
||||
BINARY = irc
|
||||
#MANPAGES1 = irc.1
|
||||
|
||||
OBJS=\
|
||||
compat.o \
|
||||
connection.o \
|
||||
database.o \
|
||||
irc.o \
|
||||
scrollback.o \
|
||||
string.o \
|
||||
ui.o \
|
||||
|
||||
all: $(BINARY)
|
||||
|
||||
.PHONY: all install clean
|
||||
|
||||
$(OBJS): config.h
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -std=gnu11 $(CFLAGS) $(CPPFLAGS) -c $< -o $@
|
||||
|
||||
$(BINARY): $(OBJS)
|
||||
$(CC) $(CFLAGS) $(OBJS) -o $(BINARY) $(LIBS)
|
||||
|
||||
install: all
|
||||
mkdir -p $(DESTDIR)$(BINDIR)
|
||||
install $(BINARY) $(DESTDIR)$(BINDIR)
|
||||
#mkdir -p $(DESTDIR)$(MANDIR)/man1
|
||||
#install $(MANPAGES1) $(DESTDIR)$(MANDIR)/man1
|
||||
|
||||
clean:
|
||||
rm -f $(BINARY)
|
||||
rm -f $(OBJS) *.o
|
||||
|
||||
TESTS=\
|
||||
have-explicit_bzero \
|
||||
have-reallocarray \
|
||||
have-strlcat \
|
||||
have-strlcpy \
|
||||
|
||||
include ../build-aux/config.mak
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* compat.c
|
||||
* Compatibility.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "compat.h"
|
||||
|
||||
#ifndef HAVE_EXPLICIT_BZERO
|
||||
void explicit_bzero(void* buffer, size_t size)
|
||||
{
|
||||
memset(buffer, 0, size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_REALLOCARRAY
|
||||
void* reallocarray(void* ptr, size_t nmemb, size_t size)
|
||||
{
|
||||
if ( size && nmemb && SIZE_MAX / size < nmemb )
|
||||
return errno = ENOMEM, (void*) NULL;
|
||||
return realloc(ptr, nmemb * size);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_STRLCAT
|
||||
size_t strlcat(char* restrict dest, const char* restrict src, size_t size)
|
||||
{
|
||||
size_t dest_len = strnlen(dest, size);
|
||||
if ( size <= dest_len )
|
||||
return dest_len + strlen(src);
|
||||
return dest_len + strlcpy(dest + dest_len, src, size - dest_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_STRLCPY
|
||||
size_t strlcpy(char* restrict dest, const char* restrict src, size_t size)
|
||||
{
|
||||
if ( !size )
|
||||
return strlen(src);
|
||||
size_t result;
|
||||
for ( result = 0; result < size-1 && src[result]; result++ )
|
||||
dest[result] = src[result];
|
||||
dest[result] = '\0';
|
||||
return result + strlen(src + result);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* combat.h
|
||||
* Compatibility.
|
||||
*/
|
||||
|
||||
#ifndef COMPAT_H
|
||||
#define COMPAT_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifndef HAVE_EXPLICIT_BZERO
|
||||
void explicit_bzero(void*, size_t);
|
||||
#endif
|
||||
#ifndef HAVE_REALLOCARRAY
|
||||
void* reallocarray(void*, size_t, size_t);
|
||||
#endif
|
||||
#ifndef HAVE_STRLCAT
|
||||
size_t strlcat(char* restrict, const char* restrict, size_t);
|
||||
#endif
|
||||
#ifndef HAVE_STRLCPY
|
||||
size_t strlcpy(char* restrict, const char* restrict, size_t);
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,549 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* connection.c
|
||||
* IRC protocol.
|
||||
*/
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "compat.h"
|
||||
#include "connection.h"
|
||||
|
||||
void dump_error(const char* message,
|
||||
size_t message_size,
|
||||
const struct timespec* when)
|
||||
{
|
||||
// TODO: Send the error somewhere appropriate in the UI.
|
||||
|
||||
fprintf(stderr, "\e[91m");
|
||||
struct tm tm;
|
||||
gmtime_r(&when->tv_sec, &tm);
|
||||
fprintf(stderr, "[%i-%02i-%02i %02i:%02i:%02i %09li] ",
|
||||
tm.tm_year + 1900,
|
||||
tm.tm_mon + 1,
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
when->tv_nsec);
|
||||
for ( size_t i = 0; i < message_size; i++ )
|
||||
{
|
||||
if ( message[i] == '\r' )
|
||||
continue;
|
||||
else if ( message[i] == '\n' )
|
||||
continue;
|
||||
else if ( (unsigned char) message[i] < 32 )
|
||||
{
|
||||
fprintf(stderr, "\e[31m");
|
||||
fprintf(stderr, "\\x%02X", (unsigned char) message[i]);
|
||||
fprintf(stderr, "\e[91m");
|
||||
}
|
||||
fputc((unsigned char) message[i], stderr);
|
||||
}
|
||||
fprintf(stderr, "\e[m\n");
|
||||
}
|
||||
|
||||
void dump_outgoing(const char* message,
|
||||
size_t message_size,
|
||||
const struct timespec* when)
|
||||
{
|
||||
return; // TODO: Remove this, or adopt it for a logging mechanism.
|
||||
|
||||
fprintf(stderr, "\e[92m");
|
||||
struct tm tm;
|
||||
gmtime_r(&when->tv_sec, &tm);
|
||||
fprintf(stderr, "[%i-%02i-%02i %02i:%02i:%02i %09li] ",
|
||||
tm.tm_year + 1900,
|
||||
tm.tm_mon + 1,
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
when->tv_nsec);
|
||||
for ( size_t i = 0; i < message_size; i++ )
|
||||
{
|
||||
if ( message[i] == '\r' )
|
||||
continue;
|
||||
else if ( message[i] == '\n' )
|
||||
continue;
|
||||
else if ( (unsigned char) message[i] < 32 )
|
||||
{
|
||||
fprintf(stderr, "\e[91m");
|
||||
fprintf(stderr, "\\x%02X", (unsigned char) message[i]);
|
||||
fprintf(stderr, "\e[92m");
|
||||
continue;
|
||||
}
|
||||
fputc((unsigned char) message[i], stderr);
|
||||
}
|
||||
fprintf(stderr, "\e[m\n");
|
||||
}
|
||||
|
||||
void dump_incoming(const char* message,
|
||||
size_t message_size,
|
||||
const struct timespec* when)
|
||||
{
|
||||
return; // TODO: Remove this, or adopt it for a logging mechanism.
|
||||
|
||||
fprintf(stderr, "\e[93m");
|
||||
struct tm tm;
|
||||
gmtime_r(&when->tv_sec, &tm);
|
||||
fprintf(stderr, "[%i-%02i-%02i %02i:%02i:%02i %09li] ",
|
||||
tm.tm_year + 1900,
|
||||
tm.tm_mon + 1,
|
||||
tm.tm_mday,
|
||||
tm.tm_hour,
|
||||
tm.tm_min,
|
||||
tm.tm_sec,
|
||||
when->tv_nsec);
|
||||
for ( size_t i = 0; i < message_size; i++ )
|
||||
{
|
||||
if ( message[i] == '\r' )
|
||||
continue;
|
||||
else if ( message[i] == '\n' )
|
||||
continue;
|
||||
else if ( (unsigned char) message[i] < 32 )
|
||||
{
|
||||
fprintf(stderr, "\e[91m");
|
||||
fprintf(stderr, "\\x%02X", (unsigned char) message[i]);
|
||||
fprintf(stderr, "\e[93m");
|
||||
continue;
|
||||
}
|
||||
fputc((unsigned char) message[i], stderr);
|
||||
}
|
||||
fprintf(stderr, "\e[m\n");
|
||||
}
|
||||
|
||||
void irc_error_vlinef(const char* format, va_list ap_orig)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
|
||||
va_copy(ap, ap_orig);
|
||||
char* string;
|
||||
if ( 0 <= vasprintf(&string, format, ap) )
|
||||
{
|
||||
va_end(ap);
|
||||
dump_error(string, strlen(string), &now);
|
||||
free(string);
|
||||
return;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
char buffer[512];
|
||||
va_copy(ap, ap_orig);
|
||||
if ( 0 <= vsnprintf(buffer, sizeof(buffer), format, ap) )
|
||||
{
|
||||
va_end(ap);
|
||||
dump_error(buffer, strlen(buffer), &now);
|
||||
dump_error("(vasprintf failed printing that line)",
|
||||
strlen("(vasprintf failed printing that line)"), &now);
|
||||
return;
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
dump_error(format, strlen(format), &now);
|
||||
dump_error("(vsnprintf failed printing format string)",
|
||||
strlen("(vsnprintf failed printing that format string)"), &now);
|
||||
}
|
||||
|
||||
void irc_error_linef(const char* format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
irc_error_vlinef(format, ap),
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void irc_transmit(struct irc_connection* irc_connection,
|
||||
const char* message,
|
||||
size_t message_size)
|
||||
{
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
if ( irc_connection->connectivity_error )
|
||||
return;
|
||||
dump_outgoing(message, message_size, &now);
|
||||
int fd = irc_connection->fd;
|
||||
while ( message_size )
|
||||
{
|
||||
ssize_t amount = 0;
|
||||
amount = send(fd, message, message_size, MSG_NOSIGNAL);
|
||||
if ( amount < 0 || amount == 0 )
|
||||
{
|
||||
warn("send");
|
||||
irc_connection->connectivity_error = true;
|
||||
return;
|
||||
}
|
||||
message += amount;
|
||||
message_size -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
void irc_transmit_message(struct irc_connection* irc_connection,
|
||||
const char* message,
|
||||
size_t message_size)
|
||||
{
|
||||
assert(2 <= message_size);
|
||||
assert(message[message_size - 2] == '\r');
|
||||
assert(message[message_size - 1] == '\n');
|
||||
|
||||
char buffer[512];
|
||||
if ( 512 < message_size )
|
||||
{
|
||||
memcpy(buffer, message, 510);
|
||||
buffer[510] = '\r';
|
||||
buffer[511] = '\n';
|
||||
message = buffer;
|
||||
message_size = 512;
|
||||
}
|
||||
|
||||
irc_transmit(irc_connection, message, message_size);
|
||||
|
||||
explicit_bzero(buffer, sizeof(buffer));
|
||||
}
|
||||
|
||||
void irc_receive_more_bytes(struct irc_connection* irc_connection)
|
||||
{
|
||||
if ( irc_connection->connectivity_error )
|
||||
return;
|
||||
int fd = irc_connection->fd;
|
||||
char* buffer = irc_connection->incoming_buffer;
|
||||
size_t buffer_size = sizeof(irc_connection->incoming_buffer);
|
||||
size_t buffer_used = irc_connection->incoming_amount;
|
||||
size_t buffer_free = buffer_size - buffer_used;
|
||||
if ( buffer_free == 0 )
|
||||
return;
|
||||
// TODO: Use non-blocking IO for transmitting as well so O_NONBLOCK can
|
||||
// always be used.
|
||||
// TODO: Use MSG_DONTWAIT when supported in Sortix.
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
ssize_t amount = recv(fd, buffer + buffer_used, buffer_free, 0);
|
||||
fcntl(fd, F_SETFL, flags);
|
||||
if ( amount < 0 )
|
||||
{
|
||||
if ( errno == EAGAIN || errno == EWOULDBLOCK )
|
||||
return;
|
||||
warn("recv");
|
||||
irc_connection->connectivity_error = true;
|
||||
}
|
||||
else if ( amount == 0 )
|
||||
{
|
||||
// TODO: Gracefully close the connection.
|
||||
irc_connection->connectivity_error = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
irc_connection->incoming_amount += amount;
|
||||
}
|
||||
}
|
||||
|
||||
void irc_receive_pop_bytes(struct irc_connection* irc_connection,
|
||||
char* buffer,
|
||||
size_t count)
|
||||
{
|
||||
assert(count <= irc_connection->incoming_amount);
|
||||
memcpy(buffer, irc_connection->incoming_buffer, count);
|
||||
explicit_bzero(irc_connection->incoming_buffer, count);
|
||||
memmove(irc_connection->incoming_buffer,
|
||||
irc_connection->incoming_buffer + count,
|
||||
irc_connection->incoming_amount - count);
|
||||
irc_connection->incoming_amount -= count;
|
||||
explicit_bzero(irc_connection->incoming_buffer + irc_connection->incoming_amount,
|
||||
count);
|
||||
}
|
||||
|
||||
bool irc_receive_message(struct irc_connection* irc_connection,
|
||||
char message[512],
|
||||
struct timespec* when)
|
||||
{
|
||||
if ( irc_connection->connectivity_error )
|
||||
return false;
|
||||
size_t message_usable = 0;
|
||||
while ( message_usable < irc_connection->incoming_amount &&
|
||||
irc_connection->incoming_buffer[message_usable] != '\r' &&
|
||||
irc_connection->incoming_buffer[message_usable] != '\n' )
|
||||
message_usable++;
|
||||
if ( message_usable < irc_connection->incoming_amount &&
|
||||
irc_connection->incoming_buffer[message_usable] == '\r')
|
||||
{
|
||||
message_usable++;
|
||||
if ( message_usable < irc_connection->incoming_amount &&
|
||||
irc_connection->incoming_buffer[message_usable] == '\n' )
|
||||
{
|
||||
message_usable++;
|
||||
irc_receive_pop_bytes(irc_connection, message, message_usable);
|
||||
assert(message[message_usable-2] == '\r');
|
||||
assert(message[message_usable-1] == '\n');
|
||||
message[message_usable-2] = '\0';
|
||||
message[message_usable-1] = '\0';
|
||||
// TODO: This is not always when the message did arrive.
|
||||
struct timespec now;
|
||||
clock_gettime(CLOCK_REALTIME, &now);
|
||||
dump_incoming(message, message_usable-2, &now);
|
||||
*when = now;
|
||||
return true;
|
||||
}
|
||||
else if ( message_usable < irc_connection->incoming_amount )
|
||||
{
|
||||
// TODO: Handle bad newline sequence.
|
||||
warnx("recv: bad IRC newline");
|
||||
irc_connection->connectivity_error = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( message_usable < irc_connection->incoming_amount &&
|
||||
irc_connection->incoming_buffer[message_usable] == '\n' )
|
||||
{
|
||||
// TODO: Handle bad newline sequence.
|
||||
warnx("recv: bad IRC newline");
|
||||
irc_connection->connectivity_error = true;
|
||||
return false;
|
||||
}
|
||||
if ( message_usable == 512 )
|
||||
{
|
||||
// TODO: Handle untruncated lines from the server.
|
||||
warnx("recv: overlong IRC line from server");
|
||||
irc_connection->connectivity_error = true;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void irc_transmit_string(struct irc_connection* irc_connection,
|
||||
const char* string)
|
||||
{
|
||||
char message[512];
|
||||
strncpy(message, string, 510);
|
||||
message[510] = '\0';
|
||||
message[511] = '\0';
|
||||
size_t string_truncated_length = strlen(message);
|
||||
for ( size_t i = 0; i < string_truncated_length; i++ )
|
||||
{
|
||||
if ( message[i] == '\r' )
|
||||
message[i] = ' ';
|
||||
if ( message[i] == '\n' )
|
||||
message[i] = ' ';
|
||||
}
|
||||
message[string_truncated_length + 0] = '\r';
|
||||
message[string_truncated_length + 1] = '\n';
|
||||
size_t message_length = strnlen(message, 512);
|
||||
irc_transmit_message(irc_connection, message, message_length);
|
||||
explicit_bzero(message, sizeof(message));
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 2, 0)))
|
||||
void irc_transmit_vformat(struct irc_connection* irc_connection,
|
||||
const char* format,
|
||||
va_list ap)
|
||||
{
|
||||
char* string = NULL;
|
||||
if ( vasprintf(&string, format, ap) < 0 )
|
||||
{
|
||||
// TODO: Hmm, what do we do here.
|
||||
warn("vasprintf");
|
||||
// TODO: Should the error condition be set?
|
||||
return;
|
||||
}
|
||||
irc_transmit_string(irc_connection, string);
|
||||
explicit_bzero(string, strlen(string));
|
||||
free(string);
|
||||
}
|
||||
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
void irc_transmit_format(struct irc_connection* irc_connection,
|
||||
const char* format,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
irc_transmit_vformat(irc_connection, format, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void irc_command_pass(struct irc_connection* irc_connection,
|
||||
const char* password)
|
||||
{
|
||||
irc_transmit_format(irc_connection, "PASS :%s", password);
|
||||
}
|
||||
|
||||
void irc_command_nick(struct irc_connection* irc_connection,
|
||||
const char* nick)
|
||||
{
|
||||
irc_transmit_format(irc_connection, "NICK :%s", nick);
|
||||
}
|
||||
|
||||
void irc_command_user(struct irc_connection* irc_connection,
|
||||
const char* nick,
|
||||
const char* local_hostname,
|
||||
const char* server_hostname,
|
||||
const char* real_name)
|
||||
{
|
||||
// TODO: What if there are spaces in some of these fields?
|
||||
irc_transmit_format(irc_connection, "USER %s %s %s :%s",
|
||||
nick, local_hostname, server_hostname, real_name);
|
||||
}
|
||||
|
||||
void irc_command_join(struct irc_connection* irc_connection,
|
||||
const char* channel)
|
||||
{
|
||||
irc_transmit_format(irc_connection, "JOIN :%s", channel);
|
||||
}
|
||||
|
||||
void irc_command_part(struct irc_connection* irc_connection,
|
||||
const char* channel)
|
||||
{
|
||||
irc_transmit_format(irc_connection, "PART :%s", channel);
|
||||
}
|
||||
|
||||
void irc_command_privmsg(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what)
|
||||
|
||||
{
|
||||
// TODO: Ensure where is valid.
|
||||
irc_transmit_format(irc_connection, "PRIVMSG %s :%s", where, what);
|
||||
}
|
||||
|
||||
void irc_command_privmsgf(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what_format,
|
||||
...)
|
||||
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, what_format);
|
||||
char msg[512];
|
||||
vsnprintf(msg, sizeof(msg), what_format, ap);
|
||||
irc_command_privmsg(irc_connection, where, msg);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void irc_command_notice(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what)
|
||||
|
||||
{
|
||||
// TODO: Ensure where is valid.
|
||||
irc_transmit_format(irc_connection, "NOTICE %s :%s", where, what);
|
||||
}
|
||||
|
||||
void irc_command_noticef(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what_format,
|
||||
...)
|
||||
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, what_format);
|
||||
char msg[512];
|
||||
vsnprintf(msg, sizeof(msg), what_format, ap);
|
||||
irc_command_notice(irc_connection, where, msg);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void irc_command_kick(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* who,
|
||||
const char* why)
|
||||
{
|
||||
// TODO: Ensure where and who are valid.
|
||||
if ( why )
|
||||
irc_transmit_format(irc_connection, "KICK %s %s :%s", where, who, why);
|
||||
else
|
||||
irc_transmit_format(irc_connection, "KICK %s %s", where, who);
|
||||
}
|
||||
|
||||
void irc_command_quit(struct irc_connection* irc_connection,
|
||||
const char* message)
|
||||
{
|
||||
if ( message )
|
||||
irc_transmit_format(irc_connection, "QUIT :%s", message);
|
||||
else
|
||||
irc_transmit_string(irc_connection, "QUIT");
|
||||
shutdown(irc_connection->fd, SHUT_WR);
|
||||
}
|
||||
|
||||
void irc_command_quit_malfunction(struct irc_connection* irc_connection,
|
||||
const char* message)
|
||||
{
|
||||
if ( message )
|
||||
irc_transmit_format(irc_connection, "QUIT :%s", message);
|
||||
else
|
||||
irc_transmit_string(irc_connection, "QUIT");
|
||||
shutdown(irc_connection->fd, SHUT_RDWR);
|
||||
}
|
||||
|
||||
void irc_parse_message_parameter(char* message,
|
||||
char* parameters[16],
|
||||
size_t* num_parameters_ptr)
|
||||
{
|
||||
size_t num_parameters = 0;
|
||||
while ( message[0] != '\0' )
|
||||
{
|
||||
if ( message[0] == ':' || num_parameters == (16-1) -1 )
|
||||
{
|
||||
message++;
|
||||
parameters[num_parameters++] = message;
|
||||
break;
|
||||
}
|
||||
|
||||
parameters[num_parameters++] = message;
|
||||
|
||||
size_t usable = 0;
|
||||
while ( message[usable] != '\0' && message[usable] != ' ' )
|
||||
usable++;
|
||||
|
||||
char lc = message[usable];
|
||||
message[usable] = '\0';
|
||||
|
||||
if ( lc != '\0' )
|
||||
message += usable + 1;
|
||||
else
|
||||
message += usable;
|
||||
}
|
||||
*num_parameters_ptr = num_parameters;
|
||||
}
|
||||
|
||||
void irc_parse_who(char* full, const char** who, const char** whomask)
|
||||
{
|
||||
size_t bangpos = strcspn(full, "!");
|
||||
if ( full[bangpos] == '!' )
|
||||
{
|
||||
full[bangpos] = '\0';
|
||||
*who = full;
|
||||
*whomask = full + bangpos + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
*who = full;
|
||||
*whomask = "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* connection.h
|
||||
* IRC protocol.
|
||||
*/
|
||||
|
||||
#ifndef CONNECTION_H
|
||||
#define CONNECTION_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <time.h>
|
||||
|
||||
struct irc_connection
|
||||
{
|
||||
int fd;
|
||||
bool connectivity_error;
|
||||
char incoming_buffer[512];
|
||||
size_t incoming_amount;
|
||||
};
|
||||
|
||||
__attribute__((format(printf, 1, 0)))
|
||||
void irc_error_vlinef(const char* format, va_list ap);
|
||||
__attribute__((format(printf, 1, 2)))
|
||||
void irc_error_linef(const char* format, ...);
|
||||
void irc_transmit(struct irc_connection* irc_connection,
|
||||
const char* message,
|
||||
size_t message_size);
|
||||
void irc_transmit_message(struct irc_connection* irc_connection,
|
||||
const char* message,
|
||||
size_t message_size);
|
||||
void irc_transmit_string(struct irc_connection* irc_connection,
|
||||
const char* string);
|
||||
__attribute__((format(printf, 2, 0)))
|
||||
void irc_transmit_vformat(struct irc_connection* irc_connection,
|
||||
const char* format,
|
||||
va_list ap);
|
||||
__attribute__((format(printf, 2, 3)))
|
||||
void irc_transmit_format(struct irc_connection* irc_connection,
|
||||
const char* format,
|
||||
...);
|
||||
void irc_receive_more_bytes(struct irc_connection* irc_connection);
|
||||
bool irc_receive_message(struct irc_connection* irc_connection,
|
||||
char message[512],
|
||||
struct timespec* when);
|
||||
void irc_command_pass(struct irc_connection* irc_connection,
|
||||
const char* password);
|
||||
void irc_command_nick(struct irc_connection* irc_connection,
|
||||
const char* nick);
|
||||
void irc_command_user(struct irc_connection* irc_connection,
|
||||
const char* nick,
|
||||
const char* local_hostname,
|
||||
const char* server_hostname,
|
||||
const char* real_name);
|
||||
void irc_command_join(struct irc_connection* irc_connection,
|
||||
const char* channel);
|
||||
void irc_command_part(struct irc_connection* irc_connection,
|
||||
const char* channel);
|
||||
void irc_command_privmsg(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what);
|
||||
__attribute__((format(printf, 3, 4)))
|
||||
void irc_command_privmsgf(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what_format,
|
||||
...);
|
||||
void irc_command_notice(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what);
|
||||
__attribute__((format(printf, 3, 4)))
|
||||
void irc_command_noticef(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* what_format,
|
||||
...);
|
||||
void irc_command_kick(struct irc_connection* irc_connection,
|
||||
const char* where,
|
||||
const char* who,
|
||||
const char* why);
|
||||
void irc_command_quit(struct irc_connection* irc_connection,
|
||||
const char* message);
|
||||
void irc_command_quit_malfunction(struct irc_connection* irc_connection,
|
||||
const char* message);
|
||||
void irc_parse_message_parameter(char* message,
|
||||
char* parameters[16],
|
||||
size_t* num_parameters_ptr);
|
||||
void irc_parse_who(char* full, const char** who, const char** whomask);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* database.c
|
||||
* Data structure for keeping track of channels and people.
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "database.h"
|
||||
#include "network.h"
|
||||
#include "string.h"
|
||||
|
||||
struct channel* find_channel(const struct network* state, const char* channel_name)
|
||||
{
|
||||
assert(channel_name);
|
||||
|
||||
for ( struct channel* channel = state->channels; channel; channel = channel->next_channel )
|
||||
if ( strchannelcmp(channel->name, channel_name) == 0 )
|
||||
return channel;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct channel* add_channel(struct network* state, const char* channel_name)
|
||||
{
|
||||
assert(channel_name);
|
||||
|
||||
assert(!find_channel(state, channel_name));
|
||||
struct channel* channel = (struct channel*) calloc(sizeof(struct channel), 1);
|
||||
if ( !channel )
|
||||
return NULL;
|
||||
channel->name = strdup(channel_name);
|
||||
if ( !channel->name )
|
||||
return free(channel), (struct channel*) NULL;
|
||||
channel->people = NULL;
|
||||
|
||||
channel->prev_channel = NULL;
|
||||
channel->next_channel = state->channels;
|
||||
if ( state->channels )
|
||||
state->channels->prev_channel = channel;
|
||||
state->channels = channel;
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
struct channel* get_channel(struct network* state, const char* channel_name)
|
||||
{
|
||||
assert(channel_name);
|
||||
|
||||
for ( struct channel* result = find_channel(state, channel_name); result; result = NULL )
|
||||
return result;
|
||||
return add_channel(state, channel_name);
|
||||
}
|
||||
|
||||
void remove_channel(struct network* state, struct channel* channel)
|
||||
{
|
||||
while ( channel->people )
|
||||
remove_person_from_channel(state, channel->people);
|
||||
|
||||
if ( channel->prev_channel )
|
||||
channel->prev_channel->next_channel = channel->next_channel;
|
||||
else
|
||||
state->channels = channel->next_channel;
|
||||
|
||||
if ( channel->next_channel )
|
||||
channel->next_channel->prev_channel = channel->prev_channel;
|
||||
|
||||
free(channel->name);
|
||||
free(channel);
|
||||
}
|
||||
|
||||
struct person* find_person(const struct network* state, const char* nick)
|
||||
{
|
||||
assert(nick);
|
||||
|
||||
for ( struct person* person = state->people; person; person = person->next_person )
|
||||
if ( strnickcmp(person->nick, nick) == 0 )
|
||||
return person;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct person* add_person(struct network* state, const char* nick)
|
||||
{
|
||||
assert(nick);
|
||||
|
||||
assert(!find_person(state, nick));
|
||||
struct person* person = (struct person*) calloc(sizeof(struct person), 1);
|
||||
if ( !person )
|
||||
return NULL;
|
||||
person->nick = strdup(nick);
|
||||
if ( !person->nick )
|
||||
return free(person), (struct person*) NULL;
|
||||
|
||||
person->prev_person = NULL;
|
||||
person->next_person = state->people;
|
||||
if ( state->people )
|
||||
state->people->prev_person = person;
|
||||
state->people = person;
|
||||
|
||||
return person;
|
||||
}
|
||||
|
||||
struct person* get_person(struct network* state, const char* nick)
|
||||
{
|
||||
assert(nick);
|
||||
|
||||
for ( struct person* result = find_person(state, nick); result; result = NULL )
|
||||
return result;
|
||||
return add_person(state, nick);
|
||||
}
|
||||
|
||||
void remove_person(struct network* state, struct person* person)
|
||||
{
|
||||
while ( person->channels )
|
||||
remove_person_from_channel(state, person->channels);
|
||||
|
||||
if ( person->prev_person )
|
||||
person->prev_person->next_person = person->next_person;
|
||||
else
|
||||
state->people = person->next_person;
|
||||
|
||||
if ( person->next_person )
|
||||
person->next_person->prev_person = person->prev_person;
|
||||
|
||||
free(person->nick);
|
||||
free(person);
|
||||
}
|
||||
|
||||
struct channel_person* find_person_in_channel(const struct network* state, const char* nick, const char* channel_name)
|
||||
{
|
||||
assert(nick);
|
||||
assert(channel_name);
|
||||
|
||||
struct channel* channel = find_channel(state, channel_name);
|
||||
if ( !channel )
|
||||
return NULL;
|
||||
for ( struct channel_person* channel_person = channel->people; channel_person; channel_person = channel_person->next_person_in_channel )
|
||||
{
|
||||
assert(channel_person->person);
|
||||
assert(channel_person->person->nick);
|
||||
if ( strnickcmp(channel_person->person->nick, nick) == 0 )
|
||||
return channel_person;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct channel_person* add_person_to_channel(struct network* state, struct person* person, struct channel* channel)
|
||||
{
|
||||
assert(person);
|
||||
assert(channel);
|
||||
|
||||
assert(person->nick);
|
||||
assert(channel->name);
|
||||
|
||||
assert(!find_person_in_channel(state, person->nick, channel->name));
|
||||
struct channel_person* channel_person = (struct channel_person*)
|
||||
calloc(sizeof(struct channel_person), 1);
|
||||
if ( !channel_person )
|
||||
return NULL;
|
||||
channel_person->channel = channel;
|
||||
channel_person->person = person;
|
||||
|
||||
channel_person->prev_channel_person = NULL;
|
||||
channel_person->next_channel_person = state->channel_people;
|
||||
if ( state->channel_people )
|
||||
state->channel_people->prev_channel_person = channel_person;
|
||||
state->channel_people = channel_person;
|
||||
|
||||
channel_person->prev_person_in_channel = NULL;
|
||||
channel_person->next_person_in_channel = channel->people;
|
||||
if ( channel->people )
|
||||
channel->people->prev_person_in_channel = channel_person;
|
||||
channel->people = channel_person;
|
||||
|
||||
channel_person->prev_channel_for_person = NULL;
|
||||
channel_person->next_channel_for_person = person->channels;
|
||||
if ( person->channels )
|
||||
person->channels->prev_channel_for_person = channel_person;
|
||||
person->channels = channel_person;
|
||||
|
||||
return channel_person;
|
||||
}
|
||||
|
||||
struct channel_person* get_person_in_channel(struct network* state, struct person* person, struct channel* channel)
|
||||
{
|
||||
for ( struct channel_person* result =
|
||||
find_person_in_channel(state, person->nick, channel->name); result; result = NULL )
|
||||
return result;
|
||||
return add_person_to_channel(state, person, channel);
|
||||
}
|
||||
|
||||
void remove_person_from_channel(struct network* state, struct channel_person* channel_person)
|
||||
{
|
||||
if ( state->channel_people != channel_person )
|
||||
channel_person->prev_channel_person->next_channel_person = channel_person->next_channel_person;
|
||||
else
|
||||
state->channel_people = channel_person->next_channel_person;
|
||||
|
||||
if ( channel_person->next_channel_person )
|
||||
channel_person->next_channel_person->prev_channel_person = channel_person->prev_channel_person;
|
||||
|
||||
if ( channel_person->channel->people != channel_person )
|
||||
channel_person->prev_person_in_channel->next_person_in_channel = channel_person->next_person_in_channel;
|
||||
else
|
||||
channel_person->channel->people = channel_person->next_person_in_channel;
|
||||
|
||||
if ( channel_person->next_person_in_channel )
|
||||
channel_person->next_person_in_channel->prev_person_in_channel = channel_person->prev_person_in_channel;
|
||||
|
||||
if ( channel_person->person->channels != channel_person )
|
||||
channel_person->prev_channel_for_person->next_channel_for_person = channel_person->next_channel_for_person;
|
||||
else
|
||||
channel_person->person->channels = channel_person->next_channel_for_person;
|
||||
|
||||
if ( channel_person->next_channel_for_person )
|
||||
channel_person->next_channel_for_person->prev_channel_for_person = channel_person->prev_channel_for_person;
|
||||
|
||||
free(channel_person);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* database.h
|
||||
* Data structure for keeping track of channels and people.
|
||||
*/
|
||||
|
||||
#ifndef DATABASE_H
|
||||
#define DATABASE_H
|
||||
|
||||
struct channel;
|
||||
struct channel_person;
|
||||
struct network;
|
||||
struct person;
|
||||
|
||||
struct person
|
||||
{
|
||||
struct person* prev_person;
|
||||
struct person* next_person;
|
||||
char* nick;
|
||||
struct channel_person* channels;
|
||||
bool always_observable; // myself or having private messaged me
|
||||
};
|
||||
|
||||
struct channel_person
|
||||
{
|
||||
struct channel_person* prev_channel_person;
|
||||
struct channel_person* next_channel_person;
|
||||
struct channel_person* prev_person_in_channel;
|
||||
struct channel_person* next_person_in_channel;
|
||||
struct channel_person* prev_channel_for_person;
|
||||
struct channel_person* next_channel_for_person;
|
||||
struct channel* channel;
|
||||
struct person* person;
|
||||
bool is_operator;
|
||||
bool is_voiced;
|
||||
};
|
||||
|
||||
struct channel
|
||||
{
|
||||
struct channel* prev_channel;
|
||||
struct channel* next_channel;
|
||||
char* name;
|
||||
char* topic;
|
||||
struct channel_person* people;
|
||||
};
|
||||
|
||||
struct channel* find_channel(const struct network* state, const char* channel_name);
|
||||
struct channel* add_channel(struct network* state, const char* channel_name);
|
||||
struct channel* get_channel(struct network* state, const char* channel_name);
|
||||
void remove_channel(struct network* state, struct channel* channel);
|
||||
struct person* find_person(const struct network* state, const char* nick);
|
||||
struct person* add_person(struct network* state, const char* nick);
|
||||
struct person* get_person(struct network* state, const char* nick);
|
||||
void remove_person(struct network* state, struct person* person);
|
||||
struct channel_person* find_person_in_channel(const struct network* state, const char* nick, const char* channel_name);
|
||||
struct channel_person* add_person_to_channel(struct network* state, struct person* person, struct channel* channel);
|
||||
struct channel_person* get_person_in_channel(struct network* state, struct person* person, struct channel* channel);
|
||||
void remove_person_from_channel(struct network* state, struct channel_person* channel_person);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef NETWORK_H
|
||||
#define NETWORK_H
|
||||
|
||||
struct account;
|
||||
struct channel;
|
||||
struct channel_person;
|
||||
struct person;
|
||||
|
||||
struct network
|
||||
{
|
||||
struct irc_connection* irc_connection;
|
||||
struct account* accounts;
|
||||
struct channel* channels;
|
||||
struct person* people;
|
||||
struct channel_person* channel_people;
|
||||
struct scrollback* scrollbacks;
|
||||
char* nick;
|
||||
char* real_name;
|
||||
char* password;
|
||||
char* server_hostname;
|
||||
const char* autojoin;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* scrollback.c
|
||||
* Ordered messages for display.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "compat.h"
|
||||
#include "network.h"
|
||||
#include "scrollback.h"
|
||||
#include "string.h"
|
||||
|
||||
void message_free(struct message* msg)
|
||||
{
|
||||
free(msg->who);
|
||||
free(msg->what);
|
||||
}
|
||||
|
||||
void scrollback_free(struct scrollback* sb)
|
||||
{
|
||||
if ( sb->network )
|
||||
{
|
||||
if ( sb->scrollback_prev )
|
||||
sb->scrollback_prev->scrollback_next = sb->scrollback_next;
|
||||
else
|
||||
sb->network->scrollbacks = sb->scrollback_next;
|
||||
if ( sb->scrollback_next )
|
||||
sb->scrollback_next->scrollback_prev = sb->scrollback_prev;
|
||||
sb->scrollback_prev = NULL;
|
||||
sb->scrollback_next = NULL;
|
||||
sb->network = NULL;
|
||||
}
|
||||
for ( size_t i = 0; i < sb->messages_count; i++ )
|
||||
message_free(&sb->messages[i]);
|
||||
free(sb->messages);
|
||||
free(sb->name);
|
||||
free(sb);
|
||||
}
|
||||
|
||||
struct scrollback* find_scrollback_network(const struct network* network)
|
||||
{
|
||||
// TODO: The server hostname can be a valid nick, for instance if the
|
||||
// hostname doesn't contain any dot characters.
|
||||
for ( struct scrollback* sb = network->scrollbacks;
|
||||
sb;
|
||||
sb = sb->scrollback_next )
|
||||
{
|
||||
if ( sb->name[0] == '#' )
|
||||
continue;
|
||||
if ( !strnickcmp(network->server_hostname, sb->name) )
|
||||
return sb;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct scrollback* find_scrollback(const struct network* network,
|
||||
const char* name)
|
||||
{
|
||||
assert(name);
|
||||
for ( struct scrollback* sb = network->scrollbacks;
|
||||
sb;
|
||||
sb = sb->scrollback_next )
|
||||
{
|
||||
if ( name[0] == '#' && sb->name[0] == '#' )
|
||||
{
|
||||
if ( strchannelcmp(name + 1, sb->name + 1) == 0 )
|
||||
return sb;
|
||||
}
|
||||
else if ( name[0] != '#' && sb->name[0] != '#' )
|
||||
{
|
||||
if ( strnickcmp(name + 1, sb->name + 1) == 0 )
|
||||
return sb;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct scrollback* add_scrollback(struct network* network, const char* name)
|
||||
{
|
||||
struct scrollback* sb =
|
||||
(struct scrollback*) calloc(1, sizeof(struct scrollback));
|
||||
if ( !sb )
|
||||
return NULL;
|
||||
if ( !(sb->name = strdup(name)) )
|
||||
return scrollback_free(sb), (struct scrollback*) NULL;
|
||||
sb->network = network;
|
||||
sb->scrollback_next = sb->network->scrollbacks;
|
||||
if ( sb->scrollback_next )
|
||||
sb->scrollback_next->scrollback_prev = sb;
|
||||
sb->network->scrollbacks = sb;
|
||||
return sb;
|
||||
}
|
||||
|
||||
struct scrollback* get_scrollback(struct network* network, const char* name)
|
||||
{
|
||||
struct scrollback* result = find_scrollback(network, name);
|
||||
if ( result )
|
||||
return result;
|
||||
return add_scrollback(network, name);
|
||||
}
|
||||
|
||||
bool scrollback_add_message(struct scrollback* sb,
|
||||
enum activity activity,
|
||||
const struct message* msg)
|
||||
{
|
||||
if ( sb->messages_count == sb->messages_allocated )
|
||||
{
|
||||
size_t new_allocated = 2 * sb->messages_allocated;
|
||||
if ( new_allocated == 0 )
|
||||
new_allocated = 64;
|
||||
struct message* new_messages = (struct message*)
|
||||
reallocarray(sb->messages, new_allocated, sizeof(struct message));
|
||||
if ( !new_messages )
|
||||
return false;
|
||||
sb->messages = new_messages;
|
||||
sb->messages_allocated = new_allocated;
|
||||
}
|
||||
sb->messages[sb->messages_count++] = *msg;
|
||||
size_t who_width = strlen(msg->who); // TODO: Unicode?
|
||||
if ( sb->who_width < who_width )
|
||||
sb->who_width = who_width;
|
||||
if ( sb->activity < activity )
|
||||
sb->activity = activity;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void message_timestamp(struct message* msg)
|
||||
{
|
||||
struct tm tm;
|
||||
time_t now = time(NULL);
|
||||
localtime_r(&now, &tm);
|
||||
msg->sec = tm.tm_sec;
|
||||
msg->min = tm.tm_min;
|
||||
msg->hour = tm.tm_hour;
|
||||
}
|
||||
|
||||
bool scrollback_print(struct scrollback* sb,
|
||||
enum activity activity,
|
||||
const char* who,
|
||||
const char* what)
|
||||
{
|
||||
struct message msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
message_timestamp(&msg);
|
||||
if ( (msg.who = strdup(who)) &&
|
||||
(msg.what = strdup(what)) &&
|
||||
scrollback_add_message(sb, activity, &msg) )
|
||||
return true;
|
||||
message_free(&msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool scrollback_printf(struct scrollback* sb,
|
||||
enum activity activity,
|
||||
const char* who,
|
||||
const char* whatf,
|
||||
...)
|
||||
{
|
||||
struct message msg;
|
||||
memset(&msg, 0, sizeof(msg));
|
||||
message_timestamp(&msg);
|
||||
va_list ap;
|
||||
va_start(ap, whatf);
|
||||
int len = vasprintf(&msg.what, whatf, ap);
|
||||
va_end(ap);
|
||||
if ( (msg.who = strdup(who)) &&
|
||||
0 <= len &&
|
||||
scrollback_add_message(sb, activity, &msg) )
|
||||
return true;
|
||||
message_free(&msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
void scrollback_clear(struct scrollback* sb)
|
||||
{
|
||||
for ( size_t i = 0; i < sb->messages_count; i++ )
|
||||
{
|
||||
free(sb->messages[i].who);
|
||||
free(sb->messages[i].what);
|
||||
}
|
||||
sb->messages_count = 0;
|
||||
sb->messages_allocated = 0;
|
||||
free(sb->messages);
|
||||
sb->messages = NULL;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* scrollback.c
|
||||
* Ordered messages for display.
|
||||
*/
|
||||
|
||||
#ifndef SCROLLBACK_H
|
||||
#define SCROLLBACK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
struct network;
|
||||
|
||||
enum activity
|
||||
{
|
||||
ACTIVITY_NONE,
|
||||
ACTIVITY_NONTALK,
|
||||
ACTIVITY_TALK,
|
||||
ACTIVITY_HIGHLIGHT,
|
||||
};
|
||||
|
||||
struct message
|
||||
{
|
||||
int hour;
|
||||
int min;
|
||||
int sec;
|
||||
char* who;
|
||||
char* what;
|
||||
};
|
||||
|
||||
struct scrollback
|
||||
{
|
||||
struct network* network;
|
||||
struct scrollback* scrollback_prev;
|
||||
struct scrollback* scrollback_next;
|
||||
char* name;
|
||||
struct message* messages;
|
||||
size_t messages_count;
|
||||
size_t messages_allocated;
|
||||
size_t who_width;
|
||||
enum activity activity;
|
||||
};
|
||||
|
||||
void message_free(struct message* msg);
|
||||
void scrollback_free(struct scrollback* sb);
|
||||
struct scrollback* find_scrollback_network(const struct network* network);
|
||||
struct scrollback* find_scrollback(const struct network* network,
|
||||
const char* name);
|
||||
struct scrollback* add_scrollback(struct network* network,
|
||||
const char* name);
|
||||
struct scrollback* get_scrollback(struct network* network,
|
||||
const char* name);
|
||||
bool scrollback_add_message(struct scrollback* sb,
|
||||
enum activity activity,
|
||||
const struct message* msg);
|
||||
bool scrollback_print(struct scrollback* sb,
|
||||
enum activity activity,
|
||||
const char* who,
|
||||
const char* what);
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
bool scrollback_printf(struct scrollback* sb,
|
||||
enum activity activity,
|
||||
const char* who,
|
||||
const char* whatf,
|
||||
...);
|
||||
void scrollback_clear(struct scrollback* sb);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* string.c
|
||||
* String utility functions and compatibility.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "string.h"
|
||||
|
||||
// TODO: Implement this properly in accordance with IRC RFC rules.
|
||||
int strchannelcmp(const char* a, const char* b)
|
||||
{
|
||||
return strcasecmp(a, b);
|
||||
}
|
||||
|
||||
// TODO: Implement this properly in accordance with IRC RFC rules.
|
||||
int strnickcmp(const char* a, const char* b)
|
||||
{
|
||||
return strcasecmp(a, b);
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2012, 2016 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2016 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
|
||||
|
@ -13,23 +13,16 @@
|
|||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* framebuffer.h
|
||||
* Keeps track of framebuffers.
|
||||
* string.h
|
||||
* String utility functions and compatibility.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_DISPD_FRAMEBUFFER_H
|
||||
#define INCLUDE_DISPD_FRAMEBUFFER_H
|
||||
#ifndef STRING_H
|
||||
#define STRING_H
|
||||
|
||||
struct dispd_framebuffer
|
||||
{
|
||||
struct dispd_window* window;
|
||||
uint8_t* data;
|
||||
size_t datasize;
|
||||
size_t pitch;
|
||||
int bpp;
|
||||
int width;
|
||||
int height;
|
||||
uint64_t fb_location;
|
||||
};
|
||||
#include <stddef.h>
|
||||
|
||||
int strchannelcmp(const char* a, const char* b);
|
||||
int strnickcmp(const char* a, const char* b);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,545 @@
|
|||
/*
|
||||
* Copyright (c) 2016 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.
|
||||
*
|
||||
* ui.c
|
||||
* User Interface.
|
||||
*/
|
||||
|
||||
#include <sys/ioctl.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "connection.h"
|
||||
#include "network.h"
|
||||
#include "scrollback.h"
|
||||
#include "ui.h"
|
||||
|
||||
struct cell
|
||||
{
|
||||
wchar_t c;
|
||||
int fgcolor;
|
||||
int bgcolor;
|
||||
};
|
||||
|
||||
static struct termios saved_termios;
|
||||
|
||||
void tty_show(struct cell* cells, size_t cols, size_t rows)
|
||||
{
|
||||
printf("\e[H");
|
||||
int fgcolor = -1;
|
||||
int bgcolor = -1;
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
for ( size_t r = 0; r < rows; r++ )
|
||||
{
|
||||
for ( size_t c = 0; c < cols; c++ )
|
||||
{
|
||||
struct cell* cell = &cells[r * cols + c];
|
||||
if ( fgcolor != cell->fgcolor )
|
||||
{
|
||||
printf("\e[%im", cell->fgcolor);
|
||||
fgcolor = cell->fgcolor;
|
||||
}
|
||||
if ( bgcolor != cell->bgcolor )
|
||||
{
|
||||
printf("\e[%im", cell->bgcolor);
|
||||
bgcolor = cell->bgcolor;
|
||||
}
|
||||
char mb[MB_CUR_MAX];
|
||||
size_t amount = wcrtomb(mb, cell->c, &ps);
|
||||
if ( amount == (size_t) -1 )
|
||||
continue;
|
||||
fwrite(mb, 1, amount, stdout);
|
||||
}
|
||||
if ( r + 1 != rows )
|
||||
printf("\n");
|
||||
}
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void on_sigquit(int sig)
|
||||
{
|
||||
// TODO: This is not async signal safe.
|
||||
ui_destroy(NULL);
|
||||
// TODO: Use sigaction so the handler only runs once.
|
||||
//raise(sig);
|
||||
(void) sig;
|
||||
raise(SIGKILL);
|
||||
}
|
||||
|
||||
void ui_initialize(struct ui* ui, struct network* network)
|
||||
{
|
||||
memset(ui, 0, sizeof(*ui));
|
||||
ui->network = network;
|
||||
ui->current = find_scrollback_network(network);
|
||||
struct winsize ws;
|
||||
if ( ioctl(1, TIOCGWINSZ, &ws) < 0 )
|
||||
err(1, "stdout: ioctl: TIOCGWINSZ");
|
||||
if ( tcgetattr(0, &saved_termios) < 0 )
|
||||
err(1, "stdin: tcgetattr");
|
||||
struct termios tcattr;
|
||||
memcpy(&tcattr, &saved_termios, sizeof(struct termios));
|
||||
tcattr.c_lflag &= ~(ECHO | ICANON | IEXTEN);
|
||||
tcattr.c_iflag |= ICRNL | ISIG;
|
||||
tcattr.c_cc[VMIN] = 1;
|
||||
tcattr.c_cc[VTIME] = 0;
|
||||
signal(SIGINT, SIG_IGN);
|
||||
signal(SIGQUIT, on_sigquit);
|
||||
tcsetattr(0, TCSADRAIN, &tcattr);
|
||||
if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 )
|
||||
{
|
||||
printf("\e[?1049h");
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_destroy(struct ui* ui)
|
||||
{
|
||||
// TODO.
|
||||
(void) ui;
|
||||
// TODO: This should be done in an atexit handler as well.
|
||||
if ( getenv("TERM") && strcmp(getenv("TERM"), "sortix") != 0 )
|
||||
{
|
||||
printf("\e[?1049l");
|
||||
fflush(stdout);
|
||||
}
|
||||
tcsetattr(0, TCSADRAIN, &saved_termios);
|
||||
}
|
||||
|
||||
void increment_offset(size_t* o_ptr, size_t* line_ptr, size_t cols)
|
||||
{
|
||||
if ( (*o_ptr)++ == cols )
|
||||
{
|
||||
*o_ptr = 0;
|
||||
(*line_ptr)++;
|
||||
}
|
||||
}
|
||||
|
||||
void ui_render(struct ui* ui)
|
||||
{
|
||||
mbstate_t ps;
|
||||
struct winsize ws;
|
||||
if ( ioctl(1, TIOCGWINSZ, &ws) < 0 )
|
||||
err(1, "stdout: ioctl: TIOCGWINSZ");
|
||||
size_t cols = ws.ws_col;
|
||||
size_t rows = ws.ws_row;
|
||||
|
||||
struct cell* cells = calloc(sizeof(struct cell) * cols, rows);
|
||||
if ( !cells )
|
||||
err(1, "calloc");
|
||||
|
||||
for ( size_t r = 0; r < rows; r++ )
|
||||
{
|
||||
for ( size_t c = 0; c < cols; c++ )
|
||||
{
|
||||
struct cell* cell = &cells[r * cols + c];
|
||||
cell->c = L' ';
|
||||
cell->fgcolor = 0;
|
||||
cell->bgcolor = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: What if the terminal isn't large enough?
|
||||
struct scrollback* sb = ui->current;
|
||||
|
||||
sb->activity = ACTIVITY_NONE;
|
||||
|
||||
size_t title_from = 0;
|
||||
size_t when_offset = 0;
|
||||
size_t when_width = 2 + 1 + 2 + 1 + 2;
|
||||
size_t who_offset = when_offset + when_width + 1;
|
||||
size_t who_width = sb->who_width;
|
||||
size_t div_offset = who_offset + who_width + 1;
|
||||
size_t what_offset = div_offset + 2;
|
||||
size_t what_width = cols - what_offset;
|
||||
size_t input_width = cols;
|
||||
|
||||
size_t input_num_lines = 1;
|
||||
for ( size_t i = 0, o = 0; i < ui->input_used; i++ )
|
||||
{
|
||||
wchar_t wc = ui->input[i];
|
||||
int w = wcwidth(wc);
|
||||
if ( w < 0 || w == 0 )
|
||||
continue;
|
||||
if ( input_width <= o )
|
||||
{
|
||||
input_num_lines++;
|
||||
o = 0;
|
||||
}
|
||||
o += w;
|
||||
}
|
||||
|
||||
char* title;
|
||||
if ( asprintf(&title, "%s @ %s / %s", ui->network->nick,
|
||||
ui->network->server_hostname, ui->current->name) < 0 )
|
||||
err(1, "asprintf");
|
||||
size_t title_len = strlen(title);
|
||||
size_t title_how_many = cols < title_len ? cols : title_len;
|
||||
size_t title_offset = (cols - title_how_many) / 2;
|
||||
for ( size_t i = 0; i < title_how_many; i++ )
|
||||
{
|
||||
char c = title[i];
|
||||
size_t cell_r = title_from;
|
||||
size_t cell_c = title_offset + i;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = btowc((unsigned char) c);
|
||||
}
|
||||
free(title);
|
||||
|
||||
size_t scrollbacks_from = title_from + 1;
|
||||
size_t scrollbacks_lines = 1;
|
||||
size_t scrollbacks_o = 0;
|
||||
for ( struct scrollback* iter = ui->network->scrollbacks;
|
||||
iter;
|
||||
iter = iter->scrollback_next )
|
||||
{
|
||||
if ( iter->scrollback_prev )
|
||||
{
|
||||
increment_offset(&scrollbacks_o, &scrollbacks_lines, cols);
|
||||
increment_offset(&scrollbacks_o, &scrollbacks_lines, cols);
|
||||
}
|
||||
for ( size_t i = 0; iter->name[i]; i++ )
|
||||
{
|
||||
char c = iter->name[i];
|
||||
size_t cell_r = scrollbacks_from + (scrollbacks_lines - 1);
|
||||
size_t cell_c = scrollbacks_o;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
int fgcolor = 0;
|
||||
if ( iter == sb )
|
||||
fgcolor = 1; // TODO: Boldness should be its own property.
|
||||
else if ( iter->activity == ACTIVITY_NONTALK )
|
||||
fgcolor = 31;
|
||||
else if ( iter->activity == ACTIVITY_TALK )
|
||||
fgcolor = 91;
|
||||
else if ( iter->activity == ACTIVITY_HIGHLIGHT )
|
||||
fgcolor = 94;
|
||||
cell->c = btowc((unsigned char) c);
|
||||
cell->fgcolor = fgcolor;
|
||||
increment_offset(&scrollbacks_o, &scrollbacks_lines, cols);
|
||||
}
|
||||
}
|
||||
|
||||
size_t horhigh_from = scrollbacks_from + scrollbacks_lines;
|
||||
|
||||
for ( size_t c = 0; c < cols; c++ )
|
||||
{
|
||||
size_t cell_r = horhigh_from;
|
||||
size_t cell_c = c;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = c == div_offset ? L'┬' : L'─';
|
||||
}
|
||||
|
||||
size_t sb_from = horhigh_from + 1;
|
||||
|
||||
// TODO: What if the input is too big?
|
||||
size_t input_bottom = rows - input_num_lines;
|
||||
size_t input_offset = 0;
|
||||
for ( size_t i = 0, o = 0, line = 0; i < ui->input_used; i++ )
|
||||
{
|
||||
wchar_t wc = ui->input[i];
|
||||
int w = wcwidth(wc);
|
||||
if ( w < 0 || w == 0 )
|
||||
continue;
|
||||
if ( input_width <= o )
|
||||
{
|
||||
line++;
|
||||
o = 0;
|
||||
}
|
||||
// TODO: If 1 < w.
|
||||
size_t cell_r = input_bottom + line;
|
||||
size_t cell_c = input_offset + o;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = wc;
|
||||
o += w;
|
||||
}
|
||||
|
||||
size_t horlow_from = input_bottom - 1;
|
||||
|
||||
for ( size_t c = 0; c < cols; c++ )
|
||||
{
|
||||
size_t cell_r = horlow_from;
|
||||
size_t cell_c = c;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = c == div_offset ? L'┴' : L'─';
|
||||
}
|
||||
|
||||
size_t sb_to = horlow_from;
|
||||
|
||||
for ( size_t r = sb_to - 1; r != sb_from - 1; r-- )
|
||||
{
|
||||
size_t cell_r = r;
|
||||
size_t cell_c = div_offset;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = L'│';
|
||||
}
|
||||
|
||||
for ( size_t r = sb_to - 1, m = sb->messages_count - 1;
|
||||
r != (sb_from - 1) && m != SIZE_MAX;
|
||||
r--, m-- )
|
||||
{
|
||||
struct message* msg = &sb->messages[m];
|
||||
size_t num_lines = 1;
|
||||
size_t max_lines = sb_from - r + 1;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
for ( size_t i = 0, o = 0; msg->what[i]; )
|
||||
{
|
||||
wchar_t wc;
|
||||
size_t amount = mbrtowc(&wc, msg->what + i, SIZE_MAX, &ps);
|
||||
if ( amount == (size_t) -1 || amount == (size_t) -2 )
|
||||
{
|
||||
// TODO.
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
continue;
|
||||
}
|
||||
i += amount;
|
||||
int w = wcwidth(wc);
|
||||
if ( w < 0 || w == 0 )
|
||||
continue;
|
||||
if ( what_width <= o )
|
||||
{
|
||||
num_lines++;
|
||||
o = 0;
|
||||
}
|
||||
o += w;
|
||||
}
|
||||
size_t how_many_lines = max_lines < num_lines ? max_lines : num_lines;
|
||||
size_t first_line = num_lines - how_many_lines;
|
||||
if ( 1 < how_many_lines )
|
||||
r -= how_many_lines - 1;
|
||||
if ( first_line == 0 )
|
||||
{
|
||||
char when[2 + 1 + 2 + 1 + 2 + 1 + 1];
|
||||
snprintf(when, sizeof(when), "%02i:%02i:%02i ",
|
||||
msg->hour, msg->min, msg->sec);
|
||||
for ( size_t i = 0; when[i]; i++ )
|
||||
{
|
||||
size_t cell_r = r;
|
||||
size_t cell_c = when_offset + i;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = btowc((unsigned char) when[i]);
|
||||
}
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
size_t msg_who_width = strlen(msg->who);
|
||||
size_t msg_who_how_many = who_width < msg_who_width ? who_width : msg_who_width;
|
||||
size_t msg_who_first = msg_who_width - msg_who_how_many;
|
||||
size_t msg_who_offset = who_width - msg_who_how_many;
|
||||
for ( size_t i = 0; i < msg_who_how_many; i++ )
|
||||
{
|
||||
char c = msg->who[msg_who_first + i];
|
||||
size_t cell_r = r;
|
||||
size_t cell_c = who_offset + msg_who_offset + i;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = btowc((unsigned char) c);
|
||||
}
|
||||
}
|
||||
for ( size_t i = 0, o = 0, line = 0; msg->what[i]; )
|
||||
{
|
||||
wchar_t wc;
|
||||
size_t amount = mbrtowc(&wc, msg->what + i, SIZE_MAX, &ps);
|
||||
if ( amount == (size_t) -1 || amount == (size_t) -2 )
|
||||
{
|
||||
// TODO.
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
continue;
|
||||
}
|
||||
i += amount;
|
||||
int w = wcwidth(wc);
|
||||
if ( w < 0 || w == 0 )
|
||||
continue;
|
||||
if ( what_width <= o )
|
||||
{
|
||||
line++;
|
||||
o = 0;
|
||||
}
|
||||
// TODO: If 1 < w.
|
||||
if ( first_line <= line )
|
||||
{
|
||||
size_t cell_r = r + line - first_line;
|
||||
size_t cell_c = what_offset + o;
|
||||
struct cell* cell = &cells[cell_r * cols + cell_c];
|
||||
cell->c = wc;
|
||||
}
|
||||
o += w;
|
||||
}
|
||||
}
|
||||
|
||||
(void) ui;
|
||||
|
||||
tty_show(cells, cols, rows);
|
||||
|
||||
free(cells);
|
||||
}
|
||||
|
||||
static bool is_command(const char* input,
|
||||
const char* cmd,
|
||||
const char** param)
|
||||
{
|
||||
size_t cmdlen = strlen(cmd);
|
||||
if ( strncmp(input, cmd, cmdlen) != 0 )
|
||||
return false;
|
||||
if ( !input[cmdlen] )
|
||||
{
|
||||
if ( param )
|
||||
*param = NULL;
|
||||
return true;
|
||||
}
|
||||
if ( input[cmdlen] != ' ' )
|
||||
return false;
|
||||
if ( !param )
|
||||
return false;
|
||||
*param = input + cmdlen + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_command_param(const char* input,
|
||||
const char* cmd,
|
||||
const char** param)
|
||||
{
|
||||
if ( !is_command(input, cmd, param) )
|
||||
return false;
|
||||
if ( !*param )
|
||||
return false; // TODO: Help message in scrollback.
|
||||
return true;
|
||||
}
|
||||
|
||||
void ui_input_char(struct ui* ui, char c)
|
||||
{
|
||||
wchar_t wc;
|
||||
size_t amount = mbrtowc(&wc, &c, 1, &ui->input_ps);
|
||||
if ( amount == (size_t) -2 )
|
||||
return;
|
||||
if ( amount == (size_t) -1 )
|
||||
{
|
||||
// TODO.
|
||||
memset(&ui->input_ps, 0, sizeof(ui->input_ps));
|
||||
return;
|
||||
}
|
||||
if ( wc == L'\b' || wc == 127 )
|
||||
{
|
||||
if ( 0 < ui->input_used )
|
||||
ui->input_used--;
|
||||
}
|
||||
else if ( wc == L'\f' /* ^L */ )
|
||||
{
|
||||
scrollback_clear(ui->current);
|
||||
// TODO: Schedule full redraw?
|
||||
}
|
||||
else if ( wc == L'\n' )
|
||||
{
|
||||
char input[4 * sizeof(ui->input) / sizeof(ui->input[0])];
|
||||
mbstate_t ps;
|
||||
memset(&ps, 0, sizeof(ps));
|
||||
const wchar_t* wcs = ui->input;
|
||||
size_t amount = wcsnrtombs(input, &wcs, ui->input_used, sizeof(input), &ps);
|
||||
ui->input_used = 0;
|
||||
if ( amount == (size_t) -1 )
|
||||
return;
|
||||
input[amount < sizeof(input) ? amount : amount - 1] = '\0';
|
||||
struct irc_connection* conn = ui->network->irc_connection;
|
||||
const char* who = ui->network->nick;
|
||||
const char* where = ui->current->name;
|
||||
const char* param;
|
||||
if ( input[0] == '/' && input[1] != '/' )
|
||||
{
|
||||
if ( !input[1] )
|
||||
return;
|
||||
if ( is_command_param(input, "/w", ¶m) ||
|
||||
is_command_param(input, "/window", ¶m) )
|
||||
{
|
||||
struct scrollback* sb = find_scrollback(ui->network, param);
|
||||
if ( sb )
|
||||
ui->current = sb;
|
||||
}
|
||||
else if ( is_command_param(input, "/query", ¶m) )
|
||||
{
|
||||
if ( param[0] == '#' )
|
||||
return; // TODO: Help in scrollback.
|
||||
struct scrollback* sb = get_scrollback(ui->network, param);
|
||||
if ( sb )
|
||||
ui->current = sb;
|
||||
}
|
||||
else if ( is_command_param(input, "/join", ¶m) )
|
||||
{
|
||||
irc_command_join(conn, param);
|
||||
struct scrollback* sb = get_scrollback(ui->network, param);
|
||||
if ( sb )
|
||||
ui->current = sb;
|
||||
}
|
||||
// TODO: Make it default to the current channel if any.
|
||||
else if ( is_command_param(input, "/part", ¶m) )
|
||||
{
|
||||
irc_command_part(conn, param);
|
||||
}
|
||||
else if ( is_command(input, "/quit", ¶m) )
|
||||
{
|
||||
irc_command_quit(conn, param ? param : "Quiting");
|
||||
}
|
||||
else if ( is_command_param(input, "/nick", ¶m) )
|
||||
{
|
||||
irc_command_nick(conn, param);
|
||||
}
|
||||
else if ( is_command_param(input, "/raw", ¶m) )
|
||||
{
|
||||
irc_transmit_string(conn, param);
|
||||
}
|
||||
else if ( is_command_param(input, "/me", ¶m) )
|
||||
{
|
||||
scrollback_printf(ui->current, ACTIVITY_NONE, "*", "%s %s",
|
||||
who, param);
|
||||
irc_command_privmsgf(conn, where, "\x01""ACTION %s""\x01",
|
||||
param);
|
||||
}
|
||||
else if ( is_command(input, "/clear", ¶m) )
|
||||
{
|
||||
scrollback_clear(ui->current);
|
||||
}
|
||||
// TODO: /ban
|
||||
// TODO: /ctcp
|
||||
// TODO: /deop
|
||||
// TODO: /devoice
|
||||
// TODO: /kick
|
||||
// TODO: /mode
|
||||
// TODO: /op
|
||||
// TODO: /quiet
|
||||
// TODO: /topic
|
||||
// TODO: /voice
|
||||
else
|
||||
{
|
||||
scrollback_printf(ui->current, ACTIVITY_NONE, "*",
|
||||
"%s :Unknown command", input + 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* what = input;
|
||||
if ( what[0] == '/' )
|
||||
what++;
|
||||
scrollback_print(ui->current, ACTIVITY_NONE, who, what);
|
||||
irc_command_privmsg(conn, where, what);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( ui->input_used < sizeof(ui->input) / sizeof(ui->input[0]) )
|
||||
ui->input[ui->input_used++] = wc;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2012 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2016 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
|
||||
|
@ -13,30 +13,30 @@
|
|||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*
|
||||
* session.h
|
||||
* Handles session management.
|
||||
* ui.h
|
||||
* User Interface.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDE_DISPD_SESSION_H
|
||||
#define INCLUDE_DISPD_SESSION_H
|
||||
#ifndef UI_H
|
||||
#define UI_H
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <wchar.h>
|
||||
|
||||
struct dispd_session
|
||||
struct network;
|
||||
struct scrollback;
|
||||
|
||||
struct ui
|
||||
{
|
||||
size_t refcount;
|
||||
uint64_t device;
|
||||
uint64_t connector;
|
||||
struct dispd_window* current_window;
|
||||
bool is_rgba;
|
||||
struct network* network;
|
||||
struct scrollback* current;
|
||||
wchar_t input[512];
|
||||
size_t input_used;
|
||||
mbstate_t input_ps;
|
||||
};
|
||||
|
||||
bool dispd__session_initialize(int* argc, char*** argv);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
void ui_initialize(struct ui* ui, struct network* network);
|
||||
void ui_render(struct ui* ui);
|
||||
void ui_input_char(struct ui* ui, char c);
|
||||
void ui_destroy(struct ui* ui);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,2 @@
|
|||
iso9660fs
|
||||
*.o
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,701 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2014, 2015, 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
|
||||
* 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 <errno.h>
|
||||
#include <error.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 )
|
||||
error(1, errno, "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 )
|
||||
error(1, errno, "%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 )
|
||||
error(1, errno, "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) )
|
||||
{
|
||||
//error(0, errno, "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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,279 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2014, 2015, 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
|
||||
* 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 <error.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 )
|
||||
error(1, errno, "`%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 )
|
||||
error(1, errno, "malloc");
|
||||
Filesystem* fs = new Filesystem(dev, pretend_mount_path, pvd_offset);
|
||||
if ( !fs )
|
||||
error(1, errno, "malloc");
|
||||
|
||||
// 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 )
|
||||
error(1, errno, "GetInode");
|
||||
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
|
||||
}
|
|
@ -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
|
|
@ -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,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.
|
||||
|
|
|
@ -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++ )
|
||||
{
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -102,6 +102,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)]; }
|
||||
|
|
|
@ -24,6 +24,8 @@
|
|||
#include <sortix/kernel/kernel.h>
|
||||
#include <sortix/kernel/syscall.h>
|
||||
|
||||
#include "net/tcp.h"
|
||||
|
||||
#ifndef VERSIONSTR
|
||||
#define VERSIONSTR "unknown"
|
||||
#endif
|
||||
|
@ -50,6 +52,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 )
|
||||
|
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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_ready_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;
|
||||
}
|
||||
|
|
|
@ -78,9 +78,7 @@ static const uint8_t DEVICE_CMD_RESET = 0xFF;
|
|||
|
||||
static const size_t DEVICE_RETRIES = 5;
|
||||
|
||||
// TODO: This is entirely a guess. I don't actually know what timeout is
|
||||
// suitable. GRUB seems to use 20 ms. I'll pick 50 ms to be safe.
|
||||
static const unsigned int TIMEOUT_MS = 50;
|
||||
static const unsigned int TIMEOUT_MS = 20;
|
||||
|
||||
static bool WaitInput()
|
||||
{
|
||||
|
|
|
@ -60,20 +60,6 @@ malloc/__heap_verify.o \
|
|||
netinet/if_ether/etheraddr_broadcast.o \
|
||||
netinet/in/in6addr_any.o \
|
||||
netinet/in/in6addr_loopback.o \
|
||||
regex/regcomp.o \
|
||||
regex/regerror.o \
|
||||
regex/regexec.o \
|
||||
regex/regfree.o \
|
||||
sha2/sha224hl.o \
|
||||
sha2/sha224.o \
|
||||
sha2/sha256hl.o \
|
||||
sha2/sha256.o \
|
||||
sha2/sha384hl.o \
|
||||
sha2/sha384.o \
|
||||
sha2/sha512_256hl.o \
|
||||
sha2/sha512_256.o \
|
||||
sha2/sha512hl.o \
|
||||
sha2/sha512.o \
|
||||
signal/sigaddset.o \
|
||||
signal/sigandset.o \
|
||||
signal/sigdelset.o \
|
||||
|
@ -86,7 +72,6 @@ signal/sigorset.o \
|
|||
ssp/__stack_chk_fail.o \
|
||||
stdio/asprintf.o \
|
||||
stdio/cbprintf.o \
|
||||
stdio/cbscanf.o \
|
||||
stdio/clearerr.o \
|
||||
stdio/clearerr_unlocked.o \
|
||||
stdio/dprintf.o \
|
||||
|
@ -116,8 +101,6 @@ stdio/fgets.o \
|
|||
stdio/fgets_unlocked.o \
|
||||
stdio/fileno_unlocked.o \
|
||||
stdio/flockfile.o \
|
||||
stdio/fmemopen.o \
|
||||
stdio/fnewfile.o \
|
||||
stdio/fparsemode.o \
|
||||
stdio/fprintf_unlocked.o \
|
||||
stdio/fputc.o \
|
||||
|
@ -128,8 +111,6 @@ stdio/fread.o \
|
|||
stdio/fread_unlocked.o \
|
||||
stdio/fregister.o \
|
||||
stdio/fresetfile.o \
|
||||
stdio/fscanf.o \
|
||||
stdio/fscanf_unlocked.o \
|
||||
stdio/fseek.o \
|
||||
stdio/fseeko.o \
|
||||
stdio/fseeko_unlocked.o \
|
||||
|
@ -142,28 +123,20 @@ stdio/funlockfile.o \
|
|||
stdio/funregister.o \
|
||||
stdio/fwrite.o \
|
||||
stdio/fwrite_unlocked.o \
|
||||
stdio/getdelim.o \
|
||||
stdio/getline.o \
|
||||
stdio/open_memstream.o \
|
||||
stdio/rewind.o \
|
||||
stdio/setbuf.o \
|
||||
stdio/setvbuf.o \
|
||||
stdio/setvbuf_unlocked.o \
|
||||
stdio/snprintf.o \
|
||||
stdio/sprintf.o \
|
||||
stdio/sscanf.o \
|
||||
stdio/ungetc.o \
|
||||
stdio/ungetc_unlocked.o \
|
||||
stdio/vasprintf.o \
|
||||
stdio/vcbprintf.o \
|
||||
stdio/vdprintf.o \
|
||||
stdio/vfprintf_unlocked.o \
|
||||
stdio/vfscanf.o \
|
||||
stdio/vfscanf_unlocked.o \
|
||||
stdio/vcbscanf.o \
|
||||
stdio/vsnprintf.o \
|
||||
stdio/vsprintf.o \
|
||||
stdio/vsscanf.o \
|
||||
stdlib/abort.o \
|
||||
stdlib/abs.o \
|
||||
stdlib/arc4random_buf.o \
|
||||
|
@ -278,7 +251,6 @@ wchar/wcscmp.o \
|
|||
wchar/wcscoll.o \
|
||||
wchar/wcscpy.o \
|
||||
wchar/wcscspn.o \
|
||||
wchar/wcsdup.o \
|
||||
wchar/wcsftime.o \
|
||||
wchar/wcslcat.o \
|
||||
wchar/wcslcpy.o \
|
||||
|
@ -329,6 +301,34 @@ wctype/towupper.o \
|
|||
wctype/wctype.o \
|
||||
|
||||
HOSTEDOBJS=\
|
||||
regex/regcomp.o \
|
||||
regex/regerror.o \
|
||||
regex/regexec.o \
|
||||
regex/regfree.o \
|
||||
sha2/sha224hl.o \
|
||||
sha2/sha224.o \
|
||||
sha2/sha256hl.o \
|
||||
sha2/sha256.o \
|
||||
sha2/sha384hl.o \
|
||||
sha2/sha384.o \
|
||||
sha2/sha512_256hl.o \
|
||||
sha2/sha512_256.o \
|
||||
sha2/sha512hl.o \
|
||||
sha2/sha512.o \
|
||||
stdio/cbscanf.o \
|
||||
stdio/fscanf.o \
|
||||
stdio/fscanf_unlocked.o \
|
||||
stdio/sscanf.o \
|
||||
stdio/vfscanf.o \
|
||||
stdio/vfscanf_unlocked.o \
|
||||
stdio/vcbscanf.o \
|
||||
stdio/vsscanf.o \
|
||||
stdio/fmemopen.o \
|
||||
stdio/open_memstream.o \
|
||||
stdio/getdelim.o \
|
||||
stdio/getline.o \
|
||||
stdio/fnewfile.o \
|
||||
wchar/wcsdup.o \
|
||||
blf/blowfish.o \
|
||||
$(CPUDIR)/fork.o \
|
||||
$(CPUDIR)/setjmp.o \
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2012, 2013, 2014, 2015, 2016 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2012, 2013, 2014, 2015, 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
|
||||
|
@ -117,6 +117,16 @@ struct heap_alloc
|
|||
#warning "You need to implement HEAP_CHUNK_MAGIC for your native word width"
|
||||
#endif
|
||||
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
#define MAGIC_IS_ALLOCATION_SITE(magic)( (((size_t) (magic)) & 0x3) == 0x2)
|
||||
#define ALLOCATION_SITE_OF_MAGIC(magic) ((struct __allocation_site*) ((magic) & ~0x3UL))
|
||||
#define MAGIC_OF_ALLOCATION_SITE(magic) (((size_t) (magic)) | 0x2)
|
||||
#else
|
||||
#define MAGIC_IS_ALLOCATION_SITE(magic) 0
|
||||
#define ALLOCATION_SITE_OF_MAGIC(magic) NULL
|
||||
#define MAGIC_OF_ALLOCATION_SITE(magic) NULL
|
||||
#endif
|
||||
|
||||
/* The heap is split into a number of parts that each consists of a number of
|
||||
of chunks (used and unused). The heap normally is just a single part, but if
|
||||
the address space gets too fragmented, it may not be possible to extend the
|
||||
|
@ -346,7 +356,8 @@ bool heap_chunk_is_used(struct heap_chunk* chunk)
|
|||
{
|
||||
assert(HEAP_IS_POINTER_ALIGNED(chunk, chunk->chunk_size));
|
||||
|
||||
return chunk->chunk_magic == HEAP_CHUNK_MAGIC;
|
||||
return chunk->chunk_magic == HEAP_CHUNK_MAGIC ||
|
||||
MAGIC_IS_ALLOCATION_SITE(chunk->chunk_magic);
|
||||
}
|
||||
|
||||
/* Returns the trailing structure following the given chunk. */
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2011, 2012, 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
|
||||
|
@ -242,8 +242,14 @@ int vdprintf(int fildes, const char* __restrict format, __gnuc_va_list ap)
|
|||
|
||||
/* Functions copied from elsewhere. */
|
||||
#if __USE_SORTIX
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
int asprintf_trace(struct __allocation_site*, char** __restrict, const char* __restrict, ...)
|
||||
__attribute__((__format__ (printf, 3, 4)));
|
||||
#define asprintf(a, ...) asprintf_trace(ALLOCATION_SITE, (a), __VA_ARGS__)
|
||||
#else
|
||||
int asprintf(char** __restrict, const char* __restrict, ...)
|
||||
__attribute__((__format__ (printf, 2, 3)));
|
||||
#endif
|
||||
void clearerr_unlocked(FILE* stream);
|
||||
int feof_unlocked(FILE* stream);
|
||||
int ferror_unlocked(FILE* stream);
|
||||
|
@ -255,9 +261,15 @@ int fputc_unlocked(int c, FILE* stream);
|
|||
int fputs_unlocked(const char* __restrict, FILE* __restrict stream);
|
||||
size_t fread_unlocked(void* __restrict ptr, size_t size, size_t nitems, FILE* __restrict stream);
|
||||
size_t fwrite_unlocked(const void* __restrict ptr, size_t size, size_t nitems, FILE* __restrict stream);
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
int vasprintf_trace(struct __allocation_site*, char** __restrict, const char* __restrict, __gnuc_va_list)
|
||||
__attribute__((__format__ (printf, 3, 0)));
|
||||
#define vasprintf(a, ...) vasprintf_trace(ALLOCATION_SITE, (a), __VA_ARGS__)
|
||||
#else
|
||||
int vasprintf(char** __restrict, const char* __restrict, __gnuc_va_list)
|
||||
__attribute__((__format__ (printf, 2, 0)));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Functions that are Sortix extensions. */
|
||||
#if __USE_SORTIX
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2011-2017 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2011-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
|
||||
|
@ -88,7 +88,12 @@ double atof(const char* value);
|
|||
int atoi(const char*);
|
||||
long atol(const char*);
|
||||
void* bsearch(const void*, const void*, size_t, size_t, int (*)(const void*, const void*));
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
void* calloc_trace(struct __allocation_site*, size_t, size_t);
|
||||
#define calloc(a, b) calloc_trace(ALLOCATION_SITE, (a), (b))
|
||||
#else
|
||||
void* calloc(size_t, size_t);
|
||||
#endif
|
||||
char* canonicalize_file_name(const char* path);
|
||||
char* canonicalize_file_name_at(int dirfd, const char* path);
|
||||
int clearenv(void);
|
||||
|
@ -99,7 +104,12 @@ void free(void*);
|
|||
char* getenv(const char*);
|
||||
long labs(long);
|
||||
ldiv_t ldiv(long, long);
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
void* malloc_trace(struct __allocation_site*, size_t);
|
||||
#define malloc(a) malloc_trace(ALLOCATION_SITE, (a))
|
||||
#else
|
||||
void* malloc(size_t);
|
||||
#endif
|
||||
int mblen(const char*, size_t);
|
||||
size_t mbstowcs(wchar_t* __restrict, const char* __restrict, size_t);
|
||||
int mbtowc(wchar_t *__restrict, const char* __restrict, size_t);
|
||||
|
@ -116,7 +126,12 @@ void qsort_r(void*, size_t, size_t, int (*)(const void*, const void*, void*), vo
|
|||
__attribute__((__warning__("rand() isn't random, use arc4random()")))
|
||||
#endif
|
||||
int rand(void);
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
void* realloc_trace(struct __allocation_site*, void*, size_t);
|
||||
#define realloc(a, b) realloc_trace(ALLOCATION_SITE, (a), (b))
|
||||
#else
|
||||
void* realloc(void*, size_t);
|
||||
#endif
|
||||
char* realpath(const char* __restrict, char* __restrict);
|
||||
int setenv(const char*, const char*, int);
|
||||
#if !defined(__is_sortix_libc) /* not a warning inside libc */
|
||||
|
@ -189,7 +204,12 @@ int posix_openpt(int);
|
|||
uint32_t arc4random(void);
|
||||
void arc4random_buf(void*, size_t);
|
||||
uint32_t arc4random_uniform(uint32_t);
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
void* reallocarray_trace(struct __allocation_site*, void*, size_t, size_t);
|
||||
#define reallocarray(a, b, c) reallocarray_trace(ALLOCATION_SITE, (a), (b), (c))
|
||||
#else
|
||||
void* reallocarray(void*, size_t, size_t);
|
||||
#endif
|
||||
int ptsname_r(int, char*, size_t);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2011, 2012, 2013, 2014, 2017 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2011, 2012, 2013, 2014, 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
|
||||
|
@ -91,8 +91,13 @@ void* memccpy(void* __restrict, const void* __restrict, int, size_t);
|
|||
|
||||
/* Functions from XOPEN 420 gone into POSIX 2008. */
|
||||
#if __USE_SORTIX || 420 <= __USE_XOPEN || 200809L <= __USE_POSIX
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
char* strdup_trace(struct __allocation_site*, const char*);
|
||||
#define strdup(a) strdup_trace(ALLOCATION_SITE, (a))
|
||||
#else
|
||||
char* strdup(const char*);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Functions from POSIX 2001. */
|
||||
#if __USE_SORTIX || 200112L <= __USE_POSIX
|
||||
|
@ -111,7 +116,12 @@ char* strtok_r(char* __restrict, const char* __restrict, char** __restrict);
|
|||
char* stpcpy(char* __restrict, const char* __restrict);
|
||||
char* stpncpy(char* __restrict, const char* __restrict, size_t);
|
||||
int strcoll_l(const char*, const char*, locale_t);
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
char* strndup_trace(struct __allocation_site*, const char*, size_t);
|
||||
#define strndup(a, b) strndup_trace(ALLOCATION_SITE, (a), (b))
|
||||
#else
|
||||
char* strndup(const char*, size_t);
|
||||
#endif
|
||||
size_t strnlen(const char*, size_t);
|
||||
#if __USE_SORTIX && __SORTIX_STDLIB_REDIRECTS
|
||||
const char* strsignal(int signum) __asm__ ("sortix_strsignal");
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2011, 2012, 2013, 2015 Jonas 'Sortie' Termansen.
|
||||
* Copyright (c) 2011, 2012, 2013, 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
|
||||
|
@ -128,4 +128,24 @@
|
|||
is updated to not rely on this macro. */
|
||||
#undef __SORTIX_HAS_GETSERVBYNAME__
|
||||
|
||||
#if (defined(__is_sortix_libk) || defined(__is_sortix_kernel)) && \
|
||||
defined(__TRACE_KMALLOC)
|
||||
#undef __TRACE_ALLOCATION_SITES
|
||||
#define __TRACE_ALLOCATION_SITES
|
||||
#endif
|
||||
|
||||
#ifdef __TRACE_ALLOCATION_SITES
|
||||
#include <stddef.h>
|
||||
struct __allocation_site
|
||||
{
|
||||
const char* file;
|
||||
size_t line;
|
||||
const char* function;
|
||||
size_t current_size;
|
||||
size_t allocations;
|
||||
};
|
||||
#define __TRACE_ALLOCATION_SITES
|
||||
#define ALLOCATION_SITE 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue