Compare commits

...

32 Commits

Author SHA1 Message Date
Jonas 'Sortie' Termansen bf3d066093 Add kernel(7) --firmware option. 2023-12-26 17:18:56 +01:00
Jonas 'Sortie' Termansen c0f1a19168 Add fatfs(8). 2023-12-26 17:18:56 +01:00
Jonas 'Sortie' Termansen bc48ded292 Add dosfstools port. 2023-12-26 17:18:56 +01:00
Jonas 'Sortie' Termansen 870fb38bf2 Add mtools port. 2023-12-26 17:18:56 +01:00
Jonas 'Sortie' Termansen 2ce9f50905 Add getty(8). 2023-12-26 17:18:56 +01:00
Jonas 'Sortie' Termansen f29bbcbde8 Add terminal and interrupt support to com(4). 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 93fac0fde3 Add nyan(1). 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 82d0bf2b79 Work around pty deadlock. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 9910260de7 Add cdrom mounting live environment. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen e6c6570bf0 Revert "Parallelize driver initialization."
This reverts commit 0fef08bbc4.
2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 3ae267d16c Parallelize driver initialization. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen dd08eba6fb Speed up ata(4) 400 ns waits.
Waiting for any non-zero duration currently waits for at least one timer
cycle (10 ms), which is especially expensive during early boot.

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

Try to determine the initial selected drive to save one drive selection.
2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen cf353e38af Decrease PS/2 timeouts. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 2a663892c5 Add uptime(1) -pr options. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 615747faf6 Revert "Update to bison-3.8.2."
This reverts commit b82fae810b42c5426d21c4dc153b32f086dd7fde.
2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen e83b67da24 Update to bison-3.8.2. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 8ee87fc11a Add iso9660 filesystem implementation. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 9c32119949 Add kernel virtual address space usage debug information. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 6c52971e8f Debug TCP socket state listing. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen 0fb0df0f62 Add kernel heap allocation tracing debug facility. 2023-12-26 17:18:55 +01:00
Jonas 'Sortie' Termansen ba25d9ea1f Trianglix 4. 2023-12-26 17:18:54 +01:00
Jonas 'Sortie' Termansen d95127432a Add tix-check(8). 2023-12-26 17:18:54 +01:00
Jonas 'Sortie' Termansen 1b5f3242ad Volatile release. 2023-12-26 17:18:54 +01:00
Jonas 'Sortie' Termansen 51cc59f4ca Add tix-upgrade(8). 2023-12-26 17:18:53 +01:00
Jonas 'Sortie' Termansen ec9aa03f99 Add tix-repository(8).
Support renaming, splitting, and deleting ports via RENAMES.
2023-12-26 17:17:35 +01:00
Jonas 'Sortie' Termansen ea075b6f44 Add signify port. 2023-12-26 17:17:35 +01:00
Jonas 'Sortie' Termansen 5c9d9fc9ec Add pty(1). 2023-12-26 17:17:35 +01:00
Jonas 'Sortie' Termansen 56fbc45a9f Add irc(1).
Co-authored-by: Juhani Krekelä <juhani@krekelä.fi>
2023-12-26 17:17:35 +01:00
Jonas 'Sortie' Termansen 29d7c4aa72 Add getaddrinfo(1). 2023-12-26 17:17:35 +01:00
Jonas 'Sortie' Termansen a2b31ec1cb Enable stack smash protection by default. 2023-12-26 17:17:35 +01:00
Jonas 'Sortie' Termansen 8681cac923 Enable undefined behavior sanitization by default. 2023-12-26 17:17:35 +01:00
Juhani Krekelä 13a75f84e9 Add irssi port. 2023-12-26 17:51:17 +02:00
153 changed files with 15423 additions and 243 deletions

View File

@ -20,13 +20,17 @@ display \
dnsconfig \
editor \
ext \
fat \
games \
hostname \
ifconfig \
init \
irc \
iso9660 \
kblayout \
kblayout-compiler \
login \
nyan \
ping \
regress \
rw \
@ -68,10 +72,13 @@ else
SORTIX_INCLUDE_SOURCE?=yes
endif
ISO_MOUNT?=no
include build-aux/dirs.mak
BUILD_NAME:=sortix-$(RELEASE)-$(MACHINE)
CHAIN_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).chain.tar
LIVE_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).live.tar
OVERLAY_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).overlay.tar
SRC_INITRD:=$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).src.tar
@ -231,6 +238,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 +278,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 +451,25 @@ release-all-archs:
# Initial ramdisk
$(CHAIN_INITRD).uuid:
mkdir -p `dirname $@`
uuidgen > $@
$(CHAIN_INITRD): $(CHAIN_INITRD).uuid sysroot
mkdir -p `dirname $(CHAIN_INITRD)`
rm -rf $(CHAIN_INITRD).d
mkdir -p $(CHAIN_INITRD).d
mkdir -p $(CHAIN_INITRD).d/etc
echo "UUID=`cat $(CHAIN_INITRD).uuid` / iso9660 ro 0 1" > $(CHAIN_INITRD).d/etc/fstab
mkdir -p $(CHAIN_INITRD).d/etc/init
echo require chain exit-code > $(CHAIN_INITRD).d/etc/init/default
mkdir -p $(CHAIN_INITRD).d/sbin
cp "$(SYSROOT)/sbin/init" $(CHAIN_INITRD).d/sbin
cp "$(SYSROOT)/sbin/iso9660fs" $(CHAIN_INITRD).d/sbin
LC_ALL=C ls -A $(CHAIN_INITRD).d | \
tar -cf $(CHAIN_INITRD) -C $(CHAIN_INITRD).d --numeric-owner --owner=0 --group=0 -T -
rm -rf $(CHAIN_INITRD).d
$(LIVE_INITRD): sysroot
mkdir -p `dirname $(LIVE_INITRD)`
rm -rf $(LIVE_INITRD).d
@ -452,6 +481,9 @@ $(LIVE_INITRD): sysroot
echo "include /etc/default/passwd.d/*" >> $(LIVE_INITRD).d/etc/passwd
echo "root::0:root" > $(LIVE_INITRD).d/etc/group
echo "include /etc/default/group.d/*" >> $(LIVE_INITRD).d/etc/group
(echo 'channel = $(CHANNEL)' && \
echo 'release_key = $(RELEASE_KEY)' && \
echo 'release_sig_url = $(RELEASE_SIG_URL)') > $(LIVE_INITRD).d/etc/upgrade.conf
mkdir -p $(LIVE_INITRD).d/root -m 700
cp -RT "$(SYSROOT)/etc/skel" $(LIVE_INITRD).d/root
(echo "You can view the documentation for new users by typing:" && \
@ -487,6 +519,42 @@ $(SORTIX_BUILDS_DIR):
# Bootable images
ifeq ($(ISO_MOUNT),yes)
$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso: sysroot $(CHAIN_INITRD) $(CHAIN_INITRD).uuid $(SORTIX_BUILDS_DIR)
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot
cp "$(SYSROOT)/boot/sortix.bin" $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot/sortix.bin
cp $(CHAIN_INITRD) $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot/sortix.initrd
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc
echo "UUID=`cat $(CHAIN_INITRD).uuid` / iso9660 ro 0 1" > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/fstab
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/init
echo require single-user exit-code > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/init/default
echo "root::0:0:root:/root:sh" > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/passwd
echo "include /etc/default/passwd.d/*" >> $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/passwd
echo "root::0:root" > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/group
echo "include /etc/default/group.d/*" >> $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/group
(echo 'channel = $(CHANNEL)' && \
echo 'release_key = $(RELEASE_KEY)' && \
echo 'release_sig_url = $(RELEASE_SIG_URL)') > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/etc/upgrade.conf
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/root -m 700
cp -RT "$(SYSROOT)/etc/skel" $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/root
(echo "You can view the documentation for new users by typing:" && \
echo && \
echo " man user-guide" && \
echo && \
echo "You can view the installation instructions by typing:" && \
echo && \
echo " man installation") > $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/root/welcome
if [ -d "$(SYSROOT_OVERLAY)" ]; then cp -RT "$(SYSROOT_OVERLAY)" $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso; fi
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso/boot/grub
build-aux/iso-grub-cfg.sh --platform $(HOST) --version $(VERSION) --mount $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
grub-mkrescue -o $(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso sysroot $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso -- -volid Sortix -volset_id `cat $(CHAIN_INITRD).uuid`
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
else
$(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso: sysroot $(LIVE_INITRD) $(OVERLAY_INITRD) $(SRC_INITRD) $(SYSTEM_INITRD) $(SORTIX_BUILDS_DIR)
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
mkdir -p $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
@ -527,6 +595,8 @@ else # none
endif
rm -rf $(SORTIX_BUILDS_DIR)/$(BUILD_NAME)-iso
endif
.PHONY: iso
iso: $(SORTIX_BUILDS_DIR)/$(BUILD_NAME).iso
@ -615,6 +685,7 @@ release-repository: sysroot $(SYSTEM_INITRD) $(SORTIX_RELEASE_DIR)/$(RELEASE)/re
cp $(SORTIX_REPOSITORY_DIR)/$(HOST)/$$port.tix.tar.xz $(SORTIX_RELEASE_DIR)/$(RELEASE)/repository/$(HOST) && \
cp $(SORTIX_REPOSITORY_DIR)/$(HOST)/$$port.version $(SORTIX_RELEASE_DIR)/$(RELEASE)/repository/$(HOST); \
done
tix-repository --generation=3 $(SORTIX_RELEASE_DIR)/$(RELEASE)/repository/$(HOST)
.PHONY: release-scripts
release-scripts: \

View File

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

21
build-aux/config.mak Normal file
View File

@ -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

View File

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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

2
fat/.gitignore vendored Normal file
View File

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

34
fat/Makefile Normal file
View File

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

165
fat/block.cpp Normal file
View File

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

62
fat/block.h Normal file
View File

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

220
fat/device.cpp Normal file
View File

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

64
fat/device.h Normal file
View File

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

124
fat/fat.h Normal file
View File

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

278
fat/fatfs.cpp Normal file
View File

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

33
fat/fatfs.h Normal file
View File

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

446
fat/filesystem.cpp Normal file
View File

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

88
fat/filesystem.h Normal file
View File

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

858
fat/fsmarshall.cpp Normal file
View File

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

31
fat/fsmarshall.h Normal file
View File

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

618
fat/fuse.cpp Normal file
View File

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

32
fat/fuse.h Normal file
View File

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

1015
fat/inode.cpp Normal file

File diff suppressed because it is too large Load Diff

90
fat/inode.h Normal file
View File

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

150
fat/ioleast.h Normal file
View File

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

49
fat/util.h Normal file
View File

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

View File

@ -3101,7 +3101,9 @@ static bool fsck(struct filesystem* fs)
if ( pid == 0 )
{
uninstall_signal_handler();
execlp(fs->fsck, fs->fsck, "-fp", "--", bdev_path, (const char*) NULL);
// TODO: Move to libmount abstraction.
const char* options = !strcmp(fs->fstype_name, "ext2") ? "-fp" : "-p";
execlp(fs->fsck, fs->fsck, options, "--", bdev_path, (const char*) NULL);
warning("%s: Failed to load filesystem checker: %s: %m",
bdev_path, fs->fsck);
_exit(127);

4
irc/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
irc
*.o
config.h
tests

56
irc/Makefile Normal file
View File

@ -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

65
irc/compat.c Normal file
View File

@ -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

40
irc/compat.h Normal file
View File

@ -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

549
irc/connection.c Normal file
View File

@ -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 = "";
}
}

102
irc/connection.h Normal file
View File

@ -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

237
irc/database.c Normal file
View File

@ -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);
}

73
irc/database.h Normal file
View File

@ -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

1007
irc/irc.c Normal file

File diff suppressed because it is too large Load Diff

24
irc/network.h Normal file
View File

@ -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

205
irc/scrollback.c Normal file
View File

@ -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;
}

82
irc/scrollback.h Normal file
View File

@ -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

34
irc/string.c Normal file
View File

@ -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);
}

28
irc/string.h Normal file
View File

@ -0,0 +1,28 @@
/*
* 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.h
* String utility functions and compatibility.
*/
#ifndef STRING_H
#define STRING_H
#include <stddef.h>
int strchannelcmp(const char* a, const char* b);
int strnickcmp(const char* a, const char* b);
#endif

545
irc/ui.c Normal file
View File

@ -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", &param) ||
is_command_param(input, "/window", &param) )
{
struct scrollback* sb = find_scrollback(ui->network, param);
if ( sb )
ui->current = sb;
}
else if ( is_command_param(input, "/query", &param) )
{
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", &param) )
{
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", &param) )
{
irc_command_part(conn, param);
}
else if ( is_command(input, "/quit", &param) )
{
irc_command_quit(conn, param ? param : "Quiting");
}
else if ( is_command_param(input, "/nick", &param) )
{
irc_command_nick(conn, param);
}
else if ( is_command_param(input, "/raw", &param) )
{
irc_transmit_string(conn, param);
}
else if ( is_command_param(input, "/me", &param) )
{
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", &param) )
{
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;
}
}

42
irc/ui.h Normal file
View File

@ -0,0 +1,42 @@
/*
* 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.h
* User Interface.
*/
#ifndef UI_H
#define UI_H
#include <wchar.h>
struct network;
struct scrollback;
struct ui
{
struct network* network;
struct scrollback* current;
wchar_t input[512];
size_t input_used;
mbstate_t input_ps;
};
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

2
iso9660/.gitignore vendored Normal file
View File

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

33
iso9660/Makefile Normal file
View File

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

108
iso9660/block.cpp Normal file
View File

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

53
iso9660/block.h Normal file
View File

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

107
iso9660/device.cpp Normal file
View File

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

51
iso9660/device.h Normal file
View File

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

87
iso9660/filesystem.cpp Normal file
View File

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

52
iso9660/filesystem.h Normal file
View File

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

701
iso9660/fsmarshall.cpp Normal file
View File

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

31
iso9660/fsmarshall.h Normal file
View File

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

572
iso9660/fuse.cpp Normal file
View File

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

32
iso9660/fuse.h Normal file
View File

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

614
iso9660/inode.cpp Normal file
View File

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

83
iso9660/inode.h Normal file
View File

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

150
iso9660/ioleast.h Normal file
View File

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

128
iso9660/iso9660.h Normal file
View File

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

278
iso9660/iso9660fs.cpp Normal file
View File

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

33
iso9660/iso9660fs.h Normal file
View File

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

49
iso9660/util.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011-2018, 2021-2022 Jonas 'Sortie' Termansen.
* Copyright (c) 2011-2018, 2021-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
@ -103,6 +103,8 @@
#include "x86-family/vbox.h"
#endif
struct kernel_allocation_site* first_kernel_allocation_site = NULL;
// Keep the stack size aligned with $CPU/boot.s
const size_t STACK_SIZE = 64*1024;
extern "C" { __attribute__((aligned(16))) size_t stack[STACK_SIZE / sizeof(size_t)]; }
@ -231,6 +233,13 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
if ( !(kernel_options = strdup(cmdline ? cmdline : "")) )
Panic("Failed to allocate kernel command line");
#if defined(__i386__) || defined(__x86_64__)
// TODO: Detect EFI.
kernel_firmware = "bios";
#else
#warning "Name your system firmware here"
kernel_firmware = "unknown";
#endif
int argmax = 1;
argv = new char*[argmax + 1];
@ -286,6 +295,20 @@ extern "C" void KernelInit(unsigned long magic, multiboot_info_t* bootinfo_p)
enable_network_drivers = true;
else if ( !strcmp(arg, "--no-random-seed") )
no_random_seed = true;
else if ( !strncmp(arg, "--firmware=", strlen("--firmware=")) )
{
const char* firmware = arg + strlen("--firmware=");
#if defined(__i386__) || defined(__x86_64__)
if ( !strcmp(firmware, "bios") || !strcmp(firmware, "pc") )
kernel_firmware = "bios";
else if ( !strcmp(firmware, "efi") )
kernel_firmware = "efi";
else
#endif
{
PanicF("Unsupported firmware option: %s", firmware);
}
}
else
{
Log::PrintF("\r\e[J");

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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;
}

View File

@ -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()
{

View File

@ -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 \

View File

@ -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. */

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 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
@ -20,13 +20,24 @@
#include <stdarg.h>
#include <stdio.h>
#ifdef __TRACE_ALLOCATION_SITES
int asprintf_trace(struct __allocation_site* allocation_site,
char** restrict result_ptr,
const char* restrict format,
...)
#else
int asprintf(char** restrict result_ptr,
const char* restrict format,
...)
#endif
{
va_list list;
va_start(list, format);
#ifdef __TRACE_ALLOCATION_SITES
int result = vasprintf_trace(allocation_site, result_ptr, format, list);
#else
int result = vasprintf(result_ptr, format, list);
#endif
va_end(list);
return result;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015 Jonas 'Sortie' Termansen.
* Copyright (c) 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
@ -27,6 +27,9 @@ struct vasprintf
char* buffer;
size_t used;
size_t size;
#ifdef __TRACE_ALLOCATION_SITES
struct __allocation_site* allocation_site;
#endif
};
static size_t vasprintf_callback(void* ctx, const char* string, size_t length)
@ -39,7 +42,12 @@ static size_t vasprintf_callback(void* ctx, const char* string, size_t length)
size_t new_size = 2 * state->size;
if ( new_size < needed_size )
new_size = needed_size;
#ifdef __TRACE_ALLOCATION_SITES
char* new_buffer = (char*) realloc_trace(state->allocation_site,
state->buffer, new_size);
#else
char* new_buffer = (char*) realloc(state->buffer, new_size);
#endif
if ( !new_buffer )
{
free(state->buffer);
@ -54,14 +62,26 @@ static size_t vasprintf_callback(void* ctx, const char* string, size_t length)
return length;
}
#ifdef __TRACE_ALLOCATION_SITES
int vasprintf_trace(struct __allocation_site* allocation_site,
char** restrict result_ptr,
const char* restrict format,
va_list list)
#else
int vasprintf(char** restrict result_ptr,
const char* restrict format,
va_list list)
#endif
{
struct vasprintf state;
state.used = 0;
state.size = 32;
#ifdef __TRACE_ALLOCATION_SITES
state.allocation_site = allocation_site;
if ( !(state.buffer = (char*) malloc_trace(allocation_site, state.size)) )
#else
if ( !(state.buffer = (char*) malloc(state.size)) )
#endif
return -1;
int result = vcbprintf(&state, vasprintf_callback, format, list);
if ( !state.buffer )

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 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
@ -22,12 +22,21 @@
#include <stdlib.h>
#include <string.h>
#ifdef __TRACE_ALLOCATION_SITES
void* calloc_trace(struct __allocation_site* allocation_site,
size_t nmemb, size_t size)
#else
void* calloc(size_t nmemb, size_t size)
#endif
{
if ( size && nmemb && SIZE_MAX / size < nmemb )
return errno = ENOMEM, (void*) NULL;
size_t total = nmemb * size;
#ifdef __TRACE_ALLOCATION_SITES
void* result = malloc_trace(allocation_site, total);
#else
void* result = malloc(total);
#endif
if ( !result )
return NULL;
memset(result, 0, total);

View File

@ -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
@ -39,6 +39,16 @@ void free(void* addr)
// Retrieve the chunk that contains this allocation.
struct heap_chunk* chunk = heap_data_to_chunk((uint8_t*) addr);
#ifdef __TRACE_ALLOCATION_SITES
if ( MAGIC_IS_ALLOCATION_SITE(chunk->chunk_magic) )
{
struct __allocation_site* allocation_site =
ALLOCATION_SITE_OF_MAGIC(chunk->chunk_magic);
allocation_site->current_size -= chunk->chunk_size;
allocation_site->allocations--;
}
#endif
// Return the chunk to the heap.
heap_insert_chunk(chunk);

View File

@ -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
@ -31,7 +31,12 @@
#if !defined(HEAP_GUARD_DEBUG)
#ifdef __TRACE_ALLOCATION_SITES
void* malloc_trace(struct __allocation_site* allocation_site,
size_t original_size)
#else
void* malloc(size_t original_size)
#endif
{
if ( !heap_size_has_bin(original_size) )
return errno = ENOMEM, (void*) NULL;
@ -89,6 +94,12 @@ void* malloc(size_t original_size)
if ( heap_can_split_chunk(result_chunk, chunk_size) )
heap_split_chunk(result_chunk, chunk_size);
#ifdef __TRACE_ALLOCATION_SITES
allocation_site->current_size += result_chunk->chunk_size;
allocation_site->allocations++;
result_chunk->chunk_magic = MAGIC_OF_ALLOCATION_SITE(allocation_site);
#endif
__heap_verify();
__heap_unlock();

View File

@ -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
@ -31,10 +31,20 @@
#if !defined(HEAP_GUARD_DEBUG)
#ifdef __TRACE_ALLOCATION_SITES
void* realloc_trace(struct __allocation_site* new_allocation_site,
void* ptr,
size_t requested_size)
#else
void* realloc(void* ptr, size_t requested_size)
#endif
{
if ( !ptr )
#ifdef __TRACE_ALLOCATION_SITES
return malloc_trace(new_allocation_site, requested_size);
#else
return malloc(requested_size);
#endif
if ( !heap_size_has_bin(requested_size) )
return errno = ENOMEM, (void*) NULL;
@ -55,7 +65,14 @@ void* realloc(void* ptr, size_t requested_size)
// Retrieve the chunk that contains this allocation.
struct heap_chunk* chunk = heap_data_to_chunk((uint8_t*) ptr);
assert(chunk->chunk_magic == HEAP_CHUNK_MAGIC);
#ifdef __TRACE_ALLOCATION_SITES
assert(MAGIC_IS_ALLOCATION_SITE(chunk->chunk_magic));
struct __allocation_site* allocation_site =
ALLOCATION_SITE_OF_MAGIC(chunk->chunk_magic);
#endif
assert(chunk->chunk_magic == HEAP_CHUNK_MAGIC ||
MAGIC_IS_ALLOCATION_SITE(chunk->chunk_magic));
assert(heap_chunk_to_post(chunk)->chunk_magic == HEAP_CHUNK_MAGIC);
assert(heap_chunk_to_post(chunk)->chunk_size == chunk->chunk_size);
@ -72,8 +89,17 @@ void* realloc(void* ptr, size_t requested_size)
if ( requested_chunk_size < chunk->chunk_size )
{
assert(requested_chunk_size <= chunk->chunk_size);
#ifdef __TRACE_ALLOCATION_SITES
allocation_site->current_size -= chunk->chunk_size;
allocation_site->allocations--;
#endif
if ( heap_can_split_chunk(chunk, requested_chunk_size) )
heap_split_chunk(chunk, requested_chunk_size);
#ifdef __TRACE_ALLOCATION_SITES
allocation_site->current_size += chunk->chunk_size;
allocation_site->allocations++;
chunk->chunk_magic = MAGIC_OF_ALLOCATION_SITE(allocation_site);
#endif
__heap_verify();
__heap_unlock();
return heap_chunk_to_data(chunk);
@ -88,11 +114,20 @@ void* realloc(void* ptr, size_t requested_size)
!heap_chunk_is_used(right) &&
requested_chunk_size <= chunk->chunk_size + right->chunk_size )
{
#ifdef __TRACE_ALLOCATION_SITES
allocation_site->current_size -= chunk->chunk_size;
allocation_site->allocations--;
#endif
heap_remove_chunk(right);
heap_chunk_format((uint8_t*) chunk, chunk->chunk_size + right->chunk_size);
assert(requested_chunk_size <= chunk->chunk_size);
if ( heap_can_split_chunk(chunk, requested_chunk_size) )
heap_split_chunk(chunk, requested_chunk_size);
#ifdef __TRACE_ALLOCATION_SITES
allocation_site->current_size += chunk->chunk_size;
allocation_site->allocations++;
chunk->chunk_magic = MAGIC_OF_ALLOCATION_SITE(allocation_site);
#endif
__heap_verify();
__heap_unlock();
return heap_chunk_to_data(chunk);
@ -108,7 +143,11 @@ void* realloc(void* ptr, size_t requested_size)
assert(orignal_ptr_size < requested_size);
#ifdef __TRACE_ALLOCATION_SITES
void* result = malloc_trace(allocation_site, requested_size);
#else
void* result = malloc(requested_size);
#endif
if ( !result )
return (void*) NULL;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 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
@ -21,9 +21,18 @@
#include <stdint.h>
#include <stdlib.h>
#ifdef __TRACE_ALLOCATION_SITES
void* reallocarray_trace(struct __allocation_site* allocation_site,
void* ptr, size_t nmemb, size_t size)
#else
void* reallocarray(void* ptr, size_t nmemb, size_t size)
#endif
{
if ( size && nmemb && SIZE_MAX / size < nmemb )
return errno = ENOMEM, (void*) NULL;
#ifdef __TRACE_ALLOCATION_SITES
return realloc_trace(allocation_site, ptr, nmemb * size);
#else
return realloc(ptr, nmemb * size);
#endif
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 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
@ -20,10 +20,18 @@
#include <stdlib.h>
#include <string.h>
#ifdef __TRACE_ALLOCATION_SITES
char* strdup_trace(struct __allocation_site* allocation_site, const char* input)
#else
char* strdup(const char* input)
#endif
{
size_t input_length = strlen(input);
#ifdef __TRACE_ALLOCATION_SITES
char* result = (char*) malloc_trace(allocation_site, input_length + 1);
#else
char* result = (char*) malloc(input_length + 1);
#endif
if ( !result )
return NULL;
memcpy(result, input, input_length + 1);

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2012, 2014 Jonas 'Sortie' Termansen.
* Copyright (c) 2011, 2012, 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
@ -20,10 +20,19 @@
#include <stdlib.h>
#include <string.h>
#ifdef __TRACE_ALLOCATION_SITES
char* strndup_trace(struct __allocation_site* allocation_site,
const char* input, size_t n)
#else
char* strndup(const char* input, size_t n)
#endif
{
size_t input_size = strnlen(input, n);
#ifdef __TRACE_ALLOCATION_SITES
char* result = (char*) malloc_trace(allocation_site, input_size + 1);
#else
char* result = (char*) malloc(input_size + 1);
#endif
if ( !result )
return NULL;
memcpy(result, input, input_size);

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