Index: .gitignore ================================================================== --- .gitignore +++ .gitignore @@ -1,7 +1,8 @@ *.a *.bundle +*.dep *.dll *.dylib *.library *.o *.orig Index: build-aux/m4/buildsys.m4 ================================================================== --- build-aux/m4/buildsys.m4 +++ build-aux/m4/buildsys.m4 @@ -1,11 +1,11 @@ dnl dnl Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2016, 2017, -dnl 2018 -dnl Jonathan Schleifer +dnl 2018, 2020 +dnl Jonathan Schleifer dnl -dnl https://heap.zone/git/?p=buildsys.git +dnl https://git.nil.im/buildsys.git dnl dnl Permission to use, copy, modify, and/or distribute this software for any dnl purpose with or without fee is hereby granted, provided that the above dnl copyright notice and this permission notice is present in all copies. dnl @@ -35,14 +35,18 @@ esac ;; esac AC_CONFIG_COMMANDS_PRE([ - AC_SUBST(CC_DEPENDS, $GCC) - AC_SUBST(CXX_DEPENDS, $GXX) - AC_SUBST(OBJC_DEPENDS, $GOBJC) - AC_SUBST(OBJCXX_DEPENDS, $GOBJCXX) + AS_IF([test x"$GCC" = x"yes"], + [AC_SUBST(DEP_CFLAGS, '-MD -MF $${out%.o}.dep')]) + AS_IF([test x"$GXX" = x"yes"], + [AC_SUBST(DEP_CXXFLAGS, '-MD -MF $${out%.o}.dep')]) + AS_IF([test x"$GOBJC" = x"yes"], + [AC_SUBST(DEP_OBJCFLAGS, '-MD -MF $${out%.o}.dep')]) + AS_IF([test x"$GOBJCXX" = x"yes"], + [AC_SUBST(DEP_OBJCXXFLAGS, '-MD -MF $${out%.o}.dep')]) AC_SUBST(AMIGA_LIB_CFLAGS) AC_SUBST(AMIGA_LIB_LDFLAGS) AC_PATH_PROG(TPUT, tput) @@ -102,19 +106,10 @@ AC_SUBST(TERM_SETAF6, "$($TPUT AF 6 2>/dev/null)") fi ]) ]) - - AC_CONFIG_COMMANDS_POST([ - ${as_echo:="echo"} ${as_me:="configure"}": touching .deps files" - for i in $(find . -name Makefile); do - DEPSFILE="$(dirname $i)/.deps" - test -f "$DEPSFILE" && rm "$DEPSFILE" - touch -t 0001010000 "$DEPSFILE" - done - ]) ]) AC_DEFUN([BUILDSYS_CHECK_IOS], [ case "$host_os" in darwin*) Index: buildsys.mk.in ================================================================== --- buildsys.mk.in +++ buildsys.mk.in @@ -1,11 +1,11 @@ # # Copyright (c) 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, -# 2017, 2018 -# Jonathan Schleifer +# 2017, 2018, 2020 +# Jonathan Schleifer # -# https://heap.zone/git/?p=buildsys.git +# https://git.nil.im/buildsys.git # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice is present in all copies. # @@ -64,15 +64,15 @@ FRAMEWORK_LDFLAGS = @FRAMEWORK_LDFLAGS@ FRAMEWORK_LDFLAGS_INSTALL_NAME = @FRAMEWORK_LDFLAGS_INSTALL_NAME@ CODESIGN = @CODESIGN@ CODESIGN_IDENTITY ?= - CLEAN_LIB = @CLEAN_LIB@ -AS_DEPENDS = @AS_DEPENDS@ -CC_DEPENDS = @CC_DEPENDS@ -CXX_DEPENDS = @CXX_DEPENDS@ -OBJC_DEPENDS = @OBJC_DEPENDS@ -OBJCXX_DEPENDS = @OBJCXX_DEPENDS@ +DEP_ASFLAGS = @DEP_ASFLAGS@ +DEP_CFLAGS = @DEP_CFLAGS@ +DEP_CXXFLAGS = @DEP_CXXFLAGS@ +DEP_OBJCFLAGS = @DEP_OBJCFLAGS@ +DEP_OBJCXXFLAGS = @DEP_OBJCXXFLAGS@ LN_S = @LN_S@ MKDIR_P = mkdir -p INSTALL = @INSTALL@ SHELL = @SHELL@ MSGFMT = @MSGFMT@ @@ -112,15 +112,20 @@ LIB_OBJS = ${OBJS:.o=.lib.o} AMIGA_LIB_OBJS = ${OBJS:.o=.amigalib.o} PLUGIN_OBJS = ${OBJS:.o=.plugin.o} +DEPS = ${OBJS:.o=.dep} \ + ${LIB_OBJS:.o=.dep} \ + ${AMIGA_LIB_OBJS:.o=.dep} \ + ${PLUGIN_OBJS:.o=.dep} + MO_FILES = ${LOCALES:.po=.mo} .SILENT: .SUFFIXES: -.SUFFIXES: .amigalib.o .beam .c .c.dep .cc .cc.dep .class .cxx .cxx.dep .d .erl .lib.o .java .mo .m .m.dep .mm .mm.dep .o .plugin.o .po .py .pyc .rc .S .S.dep .xpm +.SUFFIXES: .amigalib.o .beam .c .cc .class .cxx .d .erl .lib.o .java .mo .m .mm .o .plugin.o .po .py .pyc .rc .S .xpm .PHONY: all subdirs subdirs-after pre-depend depend install install-extra uninstall uninstall-extra clean distclean locales copy-headers-into-framework ${SUBDIRS} ${SUBDIRS_AFTER} all: ${MAKE} pre-all ${MAKE} subdirs @@ -139,86 +144,15 @@ ${DIR_ENTER}; \ ${MAKE} || exit $$?; \ ${DIR_LEAVE}; \ done -depend: pre-depend ${SRCS} - regen=0; \ - deps=""; \ - test -f .deps || regen=1; \ - for i in "" ${SRCS}; do \ - case $$i in \ - "") \ - ;; \ - *.c) \ - if test x"${CC_DEPENDS}" = x"yes"; then \ - test $$i -nt .deps && regen=1; \ - deps="$$deps $$i.dep"; \ - fi; \ - ;; \ - *.cc | *.cxx) \ - if test x"${CXX_DEPENDS}" = x"yes"; then \ - test $$i -nt .deps && regen=1; \ - deps="$$deps $$i.dep"; \ - fi; \ - ;; \ - *.m) \ - if test x"${OBJC_DEPENDS}" = x"yes"; then \ - test $$i -nt .deps && regen=1; \ - deps="$$deps $$i.dep"; \ - fi; \ - ;; \ - *.mm) \ - if test x"${OBJCXX_DEPENDS}" = x"yes"; then \ - test $$i -nt .deps && regen=1; \ - deps="$$deps $$i.dep"; \ - fi; \ - ;; \ - *.S) \ - if test x"${AS_DEPENDS}" = x"yes"; then \ - test $$i -nt .deps && regen=1; \ - deps="$$deps $$i.dep"; \ - fi; \ - ;; \ - esac; \ - done; \ - if test x"$$regen" = x"1" -a x"$$deps" != x""; then \ - ${DEPEND_STATUS}; \ - if ${MAKE} $$deps && cat $$deps >.deps; then \ - rm -f $$deps; \ - ${DEPEND_OK}; \ - else \ - :> .deps; \ - touch -t 0001010000 .deps; \ - ${DEPEND_FAILED}; \ - fi; \ - fi - -.c.c.dep: - ${CPP} ${CPPFLAGS} ${CFLAGS} -M $< | \ - sed 's/^\([^\.]*\)\.o:/\1.o \1.lib.o \1.amigalib.o \1.plugin.o:/' >$@ || \ - { rm -f $@; false; } - -.cc.cc.dep .cxx.cxx.dep: - ${CPP} ${CPPFLAGS} ${CXXFLAGS} -M $< | \ - sed 's/^\([^\.]*\)\.o:/\1.o \1.lib.o \1.amigalib.o \1.plugin.o:/' >$@ || \ - { rm -f $@; false; } - -.m.m.dep: - ${CPP} ${CPPFLAGS} ${OBJCFLAGS} -M $< | \ - sed 's/^\([^\.]*\)\.o:/\1.o \1.lib.o \1.amigalib.o \1.plugin.o:/' >$@ || \ - { rm -f $@; false; } - -.mm.mm.dep: - ${CPP} ${CPPFLAGS} ${OBJCPPFLAGS} -M $< | \ - sed 's/^\([^\.]*\)\.o:/\1.o \1.lib.o \1.amigalib.o \1.plugin.o:/' >$@ || \ - { rm -f $@; false; } - -.S.S.dep: - ${CPP} ${CPPFLAGS} ${ASFLAGS} -M $< | \ - sed 's/^\([^\.]*\)\.o:/\1.o \1.lib.o \1.amigalib.o \1.plugin.o:/' >$@ || \ - { rm -f $@; false; } +depend: pre-depend + : >.deps + for i in ${DEPS}; do \ + echo "-include \$${.CURDIR}/$$i" >>.deps; \ + done pre-depend: ${PROG} ${PROG_NOINST}: ${EXT_DEPS} ${OBJS} ${OBJS_EXTRA} ${LINK_STATUS} @@ -258,11 +192,11 @@ ${LINK_STATUS} out="$@"; \ if rm -fr $$out && ${MKDIR_P} $$out && ${MAKE} COPY_HEADERS_IF_SUBDIR=${includesubdir} COPY_HEADERS_DESTINATION=$$PWD/$@/Headers copy-headers-into-framework && if test -f Info.plist; then ${INSTALL} -m 644 Info.plist $$out/Info.plist; fi && if test -f module.modulemap; then ${MKDIR_P} $$out/Modules && ${INSTALL} -m 644 module.modulemap $$out/Modules/module.modulemap; fi && ${LD} -o $$out/$${out%.framework} ${LIB_OBJS} ${LIB_OBJS_EXTRA} ${FRAMEWORK_LDFLAGS} ${FRAMEWORK_LDFLAGS_INSTALL_NAME} ${LDFLAGS} ${FRAMEWORK_LIBS} && ${CODESIGN} -fs ${CODESIGN_IDENTITY} --timestamp=none $$out; then \ ${LINK_OK}; \ else \ - rm -fr $$out; \ + rm -fr $$out; false; \ ${LINK_FAILED}; \ fi copy-headers-into-framework: for i in "" ${SUBDIRS} ${SUBDIRS_AFTER}; do \ @@ -292,11 +226,11 @@ ${LINK_STATUS} out="$@"; \ if @LINK_PLUGIN@; then \ ${LINK_OK}; \ else \ - rm -fr $$out; \ + rm -fr $$out; false; \ ${LINK_FAILED}; \ fi ${STATIC_LIB} ${STATIC_LIB_NOINST}: ${EXT_DEPS} ${OBJS} ${OBJS_EXTRA} ${LINK_STATUS} @@ -303,12 +237,12 @@ rm -f $@ if test x"${BUILD_AND_HOST_ARE_DARWIN}" = x"yes"; then \ if /usr/bin/libtool -static -o $@ ${OBJS} ${OBJS_EXTRA}; then \ ${LINK_OK}; \ else \ + rm -f $@; false; \ ${LINK_FAILED}; \ - rm -f $@; \ fi; \ else \ out="$@"; \ objs=""; \ ars=""; \ @@ -334,12 +268,12 @@ cd ..; \ done; \ if ${AR} cr $@ $$objs && ${RANLIB} $@; then \ ${LINK_OK}; \ else \ + rm -f $@; false; \ ${LINK_FAILED}; \ - rm -f $@; \ fi; \ for i in $$ars; do \ dir=".$$(echo $$i | sed 's/\//_/g').objs"; \ rm -fr $$dir; \ done; \ @@ -350,12 +284,12 @@ rm -f $@ if test x"${BUILD_AND_HOST_ARE_DARWIN}" = x"yes"; then \ if /usr/bin/libtool -static -o $@ ${LIB_OBJS} ${LIB_OBJS_EXTRA}; then \ ${LINK_OK}; \ else \ + rm -f $@; false; \ ${LINK_FAILED}; \ - rm -f $@; \ fi; \ else \ out="$@"; \ objs=""; \ ars=""; \ @@ -381,12 +315,12 @@ cd ..; \ done; \ if ${AR} cr $@ $$objs && ${RANLIB} $@; then \ ${LINK_OK}; \ else \ + rm -f $@; false; \ ${LINK_FAILED}; \ - rm -f $@; \ fi; \ for i in $$ars; do \ dir=".$$(echo $$i | sed 's/\//_/g').objs"; \ rm -fr $$dir; \ done; \ @@ -396,75 +330,75 @@ .c.o: ${COMPILE_STATUS} in="$<"; \ out="$@"; \ - if ${CC} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} -c -o $@ $<; then \ + if ${CC} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} ${DEP_CFLAGS} -c -o $@ $<; then \ ${COMPILE_OK}; \ else \ ${COMPILE_FAILED}; \ fi .c.lib.o: ${COMPILE_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${CC} ${LIB_CFLAGS} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} -c -o $@ $<; then \ + if ${CC} ${LIB_CFLAGS} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} ${DEP_CFLAGS} -c -o $@ $<; then \ ${COMPILE_LIB_OK}; \ else \ ${COMPILE_LIB_FAILED}; \ fi .c.amigalib.o: ${COMPILE_AMIGA_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${CC} ${AMIGA_LIB_CFLAGS} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} -c -o $@ $<; then \ + if ${CC} ${AMIGA_LIB_CFLAGS} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} ${DEP_CFLAGS} -c -o $@ $<; then \ ${COMPILE_AMIGA_LIB_OK}; \ else \ ${COMPILE_AMIGA_LIB_FAILED}; \ fi .c.plugin.o: ${COMPILE_PLUGIN_STATUS} in="$<"; \ out="$@"; \ - if ${CC} ${PLUGIN_CFLAGS} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} -c -o $@ $<; then \ + if ${CC} ${PLUGIN_CFLAGS} ${CFLAGS} ${CPPFLAGS} ${CFLAGS_$<} ${CFLAGS_$@} ${DEP_CFLAGS} -c -o $@ $<; then \ ${COMPILE_PLUGIN_OK}; \ else \ ${COMPILE_PLUGIN_FAILED}; \ fi .cc.o .cxx.o: ${COMPILE_STATUS} in="$<"; \ out="$@"; \ - if ${CXX} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} -c -o $@ $<; then \ + if ${CXX} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} ${DEP_CXXFLAGS} -c -o $@ $<; then \ ${COMPILE_OK}; \ else \ ${COMPILE_FAILED}; \ fi .cc.lib.o .cxx.lib.o: ${COMPILE_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${CXX} ${LIB_CFLAGS} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} -c -o $@ $<; then \ + if ${CXX} ${LIB_CFLAGS} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} ${DEP_CXXFLAGS} -c -o $@ $<; then \ ${COMPILE_LIB_OK}; \ else \ ${COMPILE_LIB_FAILED}; \ fi .cc.amigalib.o .cxx.amigalib.o: ${COMPILE_AMIGA_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${CXX} ${AMIGA_LIB_CFLAGS} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} -c -o $@ $<; then \ + if ${CXX} ${AMIGA_LIB_CFLAGS} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} ${DEP_CXXFLAGS} -c -o $@ $<; then \ ${COMPILE_AMIGA_LIB_OK}; \ else \ ${COMPILE_AMIGA_LIB_FAILED}; \ fi .cc.plugin.o .cxx.plugin.o: ${COMPILE_PLUGIN_STATUS} in="$<"; \ out="$@"; \ - if ${CXX} ${PLUGIN_CFLAGS} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} -c -o $@ $<; then \ + if ${CXX} ${PLUGIN_CFLAGS} ${CXXFLAGS} ${CPPFLAGS} ${CXXFLAGS_$<} ${CXXFLAGS_$@} ${DEP_CXXFLAGS} -c -o $@ $<; then \ ${COMPILE_PLUGIN_OK}; \ else \ ${COMPILE_PLUGIN_FAILED}; \ fi @@ -508,75 +442,75 @@ .m.o: ${COMPILE_STATUS} in="$<"; \ out="$@"; \ - if ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} -c -o $@ $<; then \ + if ${OBJC} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} ${DEP_OBJCFLAGS} -c -o $@ $<; then \ ${COMPILE_OK}; \ else \ ${COMPILE_FAILED}; \ fi .m.lib.o: ${COMPILE_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${OBJC} ${LIB_CFLAGS} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} -c -o $@ $<; then \ + if ${OBJC} ${LIB_CFLAGS} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} ${DEP_OBJCFLAGS} -c -o $@ $<; then \ ${COMPILE_LIB_OK}; \ else \ ${COMPILE_LIB_FAILED}; \ fi .m.amigalib.o: ${COMPILE_AMIGA_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${OBJC} ${AMIGA_LIB_CFLAGS} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} -c -o $@ $<; then \ + if ${OBJC} ${AMIGA_LIB_CFLAGS} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} ${DEP_OBJCFLAGS} -c -o $@ $<; then \ ${COMPILE_AMIGA_LIB_OK}; \ else \ ${COMPILE_AMIGA_LIB_FAILED}; \ fi .m.plugin.o: ${COMPILE_PLUGIN_STATUS} in="$<"; \ out="$@"; \ - if ${OBJC} ${PLUGIN_CFLAGS} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} -c -o $@ $<; then \ + if ${OBJC} ${PLUGIN_CFLAGS} ${OBJCFLAGS} ${CPPFLAGS} ${OBJCFLAGS_$<} ${OBJCFLAGS_$@} ${DEP_OBJCFLAGS} -c -o $@ $<; then \ ${COMPILE_PLUGIN_OK}; \ else \ ${COMPILE_PLUGIN_FAILED}; \ fi .mm.o: ${COMPILE_STATUS} in="$<"; \ out="$@"; \ - if ${OBJCXX} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} -c -o $@ $<; then \ + if ${OBJCXX} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} ${DEP_OBJCXXFLAGS} -c -o $@ $<; then \ ${COMPILE_OK}; \ else \ ${COMPILE_FAILED}; \ fi .mm.lib.o: ${COMPILE_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${OBJCXX} ${LIB_CFLAGS} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} -c -o $@ $<; then \ + if ${OBJCXX} ${LIB_CFLAGS} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} ${DEP_OBJCXXFLAGS} -c -o $@ $<; then \ ${COMPILE_LIB_OK}; \ else \ ${COMPILE_LIB_FAILED}; \ fi .mm.amigalib.o: ${COMPILE_AMIGA_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${OBJCXX} ${AMIGA_LIB_CFLAGS} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} -c -o $@ $<; then \ + if ${OBJCXX} ${AMIGA_LIB_CFLAGS} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} ${DEP_OBJCXXFLAGS} -c -o $@ $<; then \ ${COMPILE_AMIGA_LIB_OK}; \ else \ ${COMPILE_AMIGA_LIB_FAILED}; \ fi .mm.plugin.o: ${COMPILE_PLUGIN_STATUS} in="$<"; \ out="$@"; \ - if ${OBJCXX} ${PLUGIN_CFLAGS} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} -c -o $@ $<; then \ + if ${OBJCXX} ${PLUGIN_CFLAGS} ${OBJCXXFLAGS} ${CPPFLAGS} ${OBJCXXFLAGS_$<} ${OBJCXXFLAGS_$@} ${DEP_OBJCXXFLAGS} -c -o $@ $<; then \ ${COMPILE_PLUGIN_OK}; \ else \ ${COMPILE_PLUGIN_FAILED}; \ fi @@ -612,29 +546,29 @@ .S.o .S.amigalib.o: ${COMPILE_STATUS} in="$<"; \ out="$@"; \ - if ${AS} ${ASFLAGS} ${CPPFLAGS} ${ASFLAGS_$<} ${ASFLAGS_$@} -c -o $@ $<; then \ + if ${AS} ${ASFLAGS} ${CPPFLAGS} ${ASFLAGS_$<} ${ASFLAGS_$@} ${DEP_ASFLAGS} -c -o $@ $<; then \ ${COMPILE_OK}; \ else \ ${COMPILE_FAILED}; \ fi .S.lib.o: ${COMPILE_LIB_STATUS} in="$<"; \ out="$@"; \ - if ${AS} ${LIB_CFLAGS} ${ASFLAGS} ${CPPFLAGS} ${ASFLAGS_$<} ${ASFLAGS_$@} -c -o $@ $<; then \ + if ${AS} ${LIB_CFLAGS} ${ASFLAGS} ${CPPFLAGS} ${ASFLAGS_$<} ${ASFLAGS_$@} ${DEP_ASFLAGS} -c -o $@ $<; then \ ${COMPILE_LIB_OK}; \ else \ ${COMPILE_LIB_FAILED}; \ fi .S.plugin.o: ${COMPILE_PLUGIN_STATUS} in="$<"; \ out="$@"; \ - if ${AS} ${PLUGIN_CFLAGS} ${ASFLAGS} ${CPPFLAGS} ${ASFLAGS_$<} ${ASFLAGS_$@} -c -o $@ $<; then \ + if ${AS} ${PLUGIN_CFLAGS} ${ASFLAGS} ${CPPFLAGS} ${ASFLAGS_$<} ${ASFLAGS_$@} ${DEP_ASFLAGS} -c -o $@ $<; then \ ${COMPILE_PLUGIN_OK}; \ else \ ${COMPILE_PLUGIN_FAILED}; \ fi @@ -909,11 +843,13 @@ ${DIR_ENTER}; \ ${MAKE} clean || exit $$?; \ ${DIR_LEAVE}; \ done - for i in "" ${DEPS} ${OBJS} ${OBJS_EXTRA} ${LIB_OBJS} ${LIB_OBJS_EXTRA} ${AMIGA_LIB_OBJS} ${AMIGA_LIB_OBJS_EXTRA} ${PLUGIN_OBJS} ${PROG} ${PROG_NOINST} ${SHARED_LIB} ${SHARED_LIB_NOINST} ${STATIC_LIB} ${STATIC_LIB_NOINST} ${STATIC_PIC_LIB} ${STATIC_PIC_LIB_NOINST} ${FRAMEWORK} ${PLUGIN} ${PLUGIN_NOINST} ${CLEAN_LIB} ${MO_FILES} ${CLEAN}; do \ + : >.deps + + for i in "" ${DEPS} ${OBJS} ${OBJS_EXTRA} ${LIB_OBJS} ${LIB_OBJS_EXTRA} ${AMIGA_LIB_OBJS} ${AMIGA_LIB_OBJS_EXTRA} ${PLUGIN_OBJS} ${PROG} ${PROG_NOINST} ${SHARED_LIB} ${SHARED_LIB_NOINST} ${AMIGA_LIB} ${AMIGA_LIB_NOINST} ${STATIC_LIB} ${STATIC_LIB_NOINST} ${STATIC_PIC_LIB} ${STATIC_PIC_LIB_NOINST} ${FRAMEWORK} ${PLUGIN} ${PLUGIN_NOINST} ${CLEAN_LIB} ${MO_FILES} ${CLEAN}; do \ test x"$$i" = x"" && continue; \ if test -f $$i -o -d $$i; then \ if rm -fr $$i; then \ ${DELETE_OK}; \ else \ @@ -953,13 +889,10 @@ print-var: printf '%s\n' '${${VAR}}' DIR_ENTER = printf "@TERM_EL@@TERM_SETAF6@Entering directory @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF6@.@TERM_SGR0@\n" "$$i"; cd $$i || exit $$? DIR_LEAVE = printf "@TERM_EL@@TERM_SETAF6@Leaving directory @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF6@.@TERM_SGR0@\n" "$$i"; cd .. || exit $$? -DEPEND_STATUS = printf "@TERM_EL@@TERM_SETAF3@Generating dependencies...@TERM_SGR0@\r" -DEPEND_OK = printf "@TERM_EL@@TERM_SETAF2@Successfully generated dependencies.@TERM_SGR0@\n" -DEPEND_FAILED = err=$$?; printf "@TERM_EL@@TERM_SETAF1@Failed to generate dependencies!@TERM_SGR0@\n"; exit $$err COMPILE_STATUS = printf "@TERM_EL@@TERM_SETAF3@Compiling @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF3@...@TERM_SGR0@\r" "$<" COMPILE_OK = printf "@TERM_EL@@TERM_SETAF2@Successfully compiled @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF2@.@TERM_SGR0@\n" "$<" COMPILE_FAILED = err=$$?; printf "@TERM_EL@@TERM_SETAF1@Failed to compile @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF1@!@TERM_SGR0@\n" "$<"; exit $$err COMPILE_LIB_STATUS = printf "@TERM_EL@@TERM_SETAF3@Compiling @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF3@ (lib)...@TERM_SGR0@\r" "$<" COMPILE_LIB_OK = printf "@TERM_EL@@TERM_SETAF2@Successfully compiled @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF2@ (lib).@TERM_SGR0@\n" "$<" @@ -978,6 +911,6 @@ INSTALL_FAILED = err=$$?; printf "@TERM_EL@@TERM_SETAF1@Failed to install @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF1@!@TERM_SGR0@\n" "$$i"; exit $$err DELETE_OK = printf "@TERM_EL@@TERM_SETAF4@Deleted @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF4@.@TERM_SGR0@\n" "$$i" DELETE_FAILED = err=$$?; printf "@TERM_EL@@TERM_SETAF1@Failed to delete @TERM_BOLD@%s@TERM_SGR0@@TERM_SETAF1@!@TERM_SGR0@\n" "$$i"; exit $$err .CURDIR ?= . -include ${.CURDIR}/.deps +-include ${.CURDIR}/.deps Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1011,10 +1011,14 @@ case "$host" in aarch64*-*-android*) # Compiler TLS is broken on AArch64 Android with Clang enable_compiler_tls="no" ;; + m68k-*-amigaos | powerpc-*-amigaos) + # Compiler TLS is broken on AmigaOS + enable_compiler_tls="no" + ;; esac AS_IF([test x"$enable_compiler_tls" != x"no"], [ AC_CHECK_HEADER(threads.h, [ AC_DEFINE(OF_HAVE_THREADS_H, 1, @@ -1757,19 +1761,26 @@ ]) ]) ], [ AC_SUBST(RUN_TESTS, "run") ]) + +AC_ARG_WITH(fish_completions, + AS_HELP_STRING([--with-fish-completions], + [install completions for the fish shell])) +AS_IF([test x"$with_fish_completions" = x"yes"], [ + AC_SUBST(FISH_COMPLETIONS, fish) +]) dnl We don't call AC_PROG_CPP, but only AC_PROG_OBJCPP and set CPP to OBJCPP dnl and add OBJCPPFLAGS to CPPFLAGS, thus we need to AC_SUBST these ourself. AC_SUBST(CPP) AC_SUBST(CPPFLAGS) dnl We use the ObjC compiler as our assembler AC_SUBST(AS, $OBJC) AC_SUBST(ASFLAGS) -AC_SUBST(AS_DEPENDS, '${OBJC_DEPENDS}') +AC_SUBST(DEP_ASFLAGS, '${DEP_OBJCFLAGS}') AC_SUBST(OBJFW_CPPFLAGS) AC_SUBST(OBJFW_OBJCFLAGS) AC_SUBST(TESTS_LIBS) Index: extra.mk.in ================================================================== --- extra.mk.in +++ extra.mk.in @@ -30,10 +30,11 @@ ENCODINGS_SRCS = @ENCODINGS_SRCS@ EXCEPTIONS_A = @EXCEPTIONS_A@ EXCEPTIONS_EXCEPTIONS_A = @EXCEPTIONS_EXCEPTIONS_A@ EXCEPTIONS_EXCEPTIONS_LIB_A = @EXCEPTIONS_EXCEPTIONS_LIB_A@ EXCEPTIONS_LIB_A = @EXCEPTIONS_LIB_A@ +FISH_COMPLETIONS = @FISH_COMPLETIONS@ FORWARDING_A = @FORWARDING_A@ FORWARDING_FORWARDING_A = @FORWARDING_FORWARDING_A@ FORWARDING_FORWARDING_LIB_A = @FORWARDING_FORWARDING_LIB_A@ FORWARDING_LIB_A = @FORWARDING_LIB_A@ INSTANCE_M = @INSTANCE_M@ Index: src/OFFile.m ================================================================== --- src/OFFile.m +++ src/OFFile.m @@ -528,20 +528,22 @@ } #endif - (void)close { - if (_handle != OF_INVALID_FILE_HANDLE) - closeHandle(_handle); + if (_handle == OF_INVALID_FILE_HANDLE) + @throw [OFNotOpenException exceptionWithObject: self]; + closeHandle(_handle); _handle = OF_INVALID_FILE_HANDLE; [super close]; } - (void)dealloc { - [self close]; + if (_handle != OF_INVALID_FILE_HANDLE) + [self close]; [super dealloc]; } @end Index: src/OFGZIPStream.m ================================================================== --- src/OFGZIPStream.m +++ src/OFGZIPStream.m @@ -63,11 +63,12 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; [_inflateStream release]; [_modificationDate release]; [super dealloc]; @@ -321,11 +322,14 @@ return (super.hasDataInReadBuffer || _stream.hasDataInReadBuffer); } - (void)close { + if (_stream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + [_stream release]; _stream = nil; [super close]; } @end Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -46,10 +46,12 @@ #import "OFOutOfRangeException.h" #import "OFTruncatedDataException.h" #import "OFUnsupportedProtocolException.h" #import "OFUnsupportedVersionException.h" #import "OFWriteFailedException.h" + +#import "socket_helpers.h" #define REDIRECTS_DEFAULT 10 @interface OFHTTPClientRequestHandler: OFObject { @@ -729,14 +731,14 @@ return self; } - (void)dealloc { - [self close]; + if (_socket != nil) + [self close]; [_handler release]; - [_socket release]; [super dealloc]; } - (size_t)lowlevelWriteBuffer: (const void *)buffer @@ -785,20 +787,22 @@ } - (void)close { if (_socket == nil) - return; + @throw [OFNotOpenException exceptionWithObject: self]; if (_toWrite > 0) @throw [OFTruncatedDataException exception]; _socket.delegate = _handler; [_socket asyncReadLine]; [_socket release]; _socket = nil; + + [super close]; } - (int)fileDescriptorForWriting { return _socket.fileDescriptorForWriting; @@ -817,11 +821,12 @@ return self; } - (void)dealloc { - [_socket release]; + if (_socket != nil) + [self close]; [super dealloc]; } - (void)setHeaders: (OFDictionary *)headers @@ -880,19 +885,13 @@ if (ret > length) @throw [OFOutOfRangeException exception]; _toRead -= ret; - if (_toRead == 0) { + if (_toRead == 0) _atEndOfStream = true; - if (!_keepAlive) { - [_socket release]; - _socket = nil; - } - } - return ret; } /* Chunked */ if (_toRead > 0) { @@ -949,13 +948,10 @@ } if (line.length > 0) @throw [OFInvalidServerReplyException exception]; - } else { - [_socket release]; - _socket = nil; } } objc_autoreleasePoolPop(pool); @@ -990,10 +986,13 @@ return (super.hasDataInReadBuffer || _socket.hasDataInReadBuffer); } - (void)close { + if (_socket == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + _atEndOfStream = false; [_socket release]; _socket = nil; Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -248,11 +248,11 @@ } - (void)dealloc { if (_socket != nil) - [self close]; /* includes [_socket release] */ + [self close]; [_server release]; [_request release]; [super dealloc]; @@ -675,11 +675,12 @@ return self; } - (void)dealloc { - [self close]; + if (_socket != nil) + [self close]; [super dealloc]; } - (bool)lowlevelIsAtEndOfStream @@ -718,12 +719,17 @@ return _socket.fileDescriptorForReading; } - (void)close { + if (_socket == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + [_socket release]; _socket = nil; + + [super close]; } @end #ifdef OF_HAVE_THREADS @implementation OFHTTPServerThread Index: src/OFHostAddressResolver.m ================================================================== --- src/OFHostAddressResolver.m +++ src/OFHostAddressResolver.m @@ -239,10 +239,17 @@ [_addresses makeImmutable]; if (_addresses.count == 0) { [_addresses release]; _addresses = nil; + + if ([exception isKindOfClass: + [OFDNSQueryFailedException class]]) + exception = [OFResolveHostFailedException + exceptionWithHost: _host + addressFamily: _addressFamily + error: [exception error]]; if (exception == nil) exception = [OFResolveHostFailedException exceptionWithHost: _host addressFamily: _addressFamily Index: src/OFInflateStream.m ================================================================== --- src/OFInflateStream.m +++ src/OFInflateStream.m @@ -206,11 +206,12 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; if (_state == HUFFMAN_TREE) if (_context.huffmanTree.codeLenTree != NULL) of_huffman_tree_release( _context.huffmanTree.codeLenTree); @@ -677,10 +678,13 @@ _bufferLength - _bufferIndex > 0); } - (void)close { + if (_stream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + /* Give back our buffer to the stream, in case it's shared */ [_stream unreadFromBuffer: _buffer + _bufferIndex length: _bufferLength - _bufferIndex]; _bufferIndex = _bufferLength = 0; Index: src/OFLHAArchive.m ================================================================== --- src/OFLHAArchive.m +++ src/OFLHAArchive.m @@ -150,11 +150,12 @@ } #endif - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; [super dealloc]; } - (OFLHAArchiveEntry *)nextEntry @@ -165,11 +166,15 @@ if (_mode != OF_LHA_ARCHIVE_MODE_READ) @throw [OFInvalidArgumentException exception]; [(OFLHAArchiveFileReadStream *)_lastReturnedStream of_skip]; - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } [_lastReturnedStream release]; _lastReturnedStream = nil; for (headerLen = 0; headerLen < 21;) { if (_stream.atEndOfStream) { @@ -224,11 +229,15 @@ if (![compressionMethod isEqual: @"-lh0-"] && ![compressionMethod isEqual: @"-lhd-"]) @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } [_lastReturnedStream release]; _lastReturnedStream = nil; _lastReturnedStream = [[OFLHAArchiveFileWriteStream alloc] of_initWithStream: (OFSeekableStream *)_stream @@ -240,13 +249,17 @@ } - (void)close { if (_stream == nil) - return; + @throw [OFNotOpenException exceptionWithObject: self]; - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } [_lastReturnedStream release]; _lastReturnedStream = nil; [_stream release]; _stream = nil; @@ -295,14 +308,13 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil || _decompressedStream != nil) + [self close]; - [_stream release]; - [_decompressedStream release]; [_entry release]; [super dealloc]; } @@ -415,10 +427,13 @@ _skipped = true; } - (void)close { + if (_stream == nil || _decompressedStream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + [self of_skip]; [_stream release]; _stream = nil; @@ -458,11 +473,12 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; [_entry release]; [super dealloc]; } @@ -511,11 +527,11 @@ - (void)close { of_offset_t offset; if (_stream == nil) - return; + @throw [OFNotOpenException exceptionWithObject: self]; _entry.uncompressedSize = _bytesWritten; _entry.compressedSize = _bytesWritten; _entry.CRC16 = _CRC16; Index: src/OFLHADecompressingStream.m ================================================================== --- src/OFLHADecompressingStream.m +++ src/OFLHADecompressingStream.m @@ -121,11 +121,12 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; if (_codeLenTree != NULL) of_huffman_tree_release(_codeLenTree); if (_litLenTree != NULL) of_huffman_tree_release(_litLenTree); @@ -514,10 +515,13 @@ _bufferLength - _bufferIndex > 0); } - (void)close { + if (_stream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + /* Give back our buffer to the stream, in case it's shared */ [_stream unreadFromBuffer: _buffer + _bufferIndex length: _bufferLength - _bufferIndex]; _bytesConsumed -= _bufferLength - _bufferIndex; _bufferIndex = _bufferLength = 0; Index: src/OFMutableURL.m ================================================================== --- src/OFMutableURL.m +++ src/OFMutableURL.m @@ -68,12 +68,17 @@ - (void)setHost: (OFString *)host { void *pool = objc_autoreleasePoolPush(); OFString *old = _URLEncodedHost; - _URLEncodedHost = [[host stringByURLEncodingWithAllowedCharacters: - [OFCharacterSet URLHostAllowedCharacterSet]] copy]; + if (of_url_is_ipv6_host(host)) + _URLEncodedHost = [[OFString alloc] + initWithFormat: @"[%@]", host]; + else + _URLEncodedHost = [[host + stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLHostAllowedCharacterSet]] copy]; [old release]; objc_autoreleasePoolPop(pool); } @@ -80,11 +85,16 @@ - (void)setURLEncodedHost: (OFString *)URLEncodedHost { OFString *old; - if (URLEncodedHost != nil) + if ([URLEncodedHost hasPrefix: @"["] && + [URLEncodedHost hasSuffix: @"]"]) { + if (!of_url_is_ipv6_host([URLEncodedHost substringWithRange: + of_range(1, URLEncodedHost.length - 2)])) + @throw [OFInvalidFormatException exception]; + } else if (URLEncodedHost != nil) of_url_verify_escaped(URLEncodedHost, [OFCharacterSet URLHostAllowedCharacterSet]); old = _URLEncodedHost; _URLEncodedHost = [URLEncodedHost copy]; Index: src/OFPollKernelEventObserver.m ================================================================== --- src/OFPollKernelEventObserver.m +++ src/OFPollKernelEventObserver.m @@ -71,13 +71,21 @@ - (void)of_addObject: (id)object fileDescriptor: (int)fd events: (short)events { - struct pollfd *FDs = _FDs.mutableItems; - size_t count = _FDs.count; - bool found = false; + struct pollfd *FDs; + size_t count; + bool found; + + if (fd < 0) + @throw [OFObserveFailedException exceptionWithObserver: self + errNo: EBADF]; + + FDs = _FDs.mutableItems; + count = _FDs.count; + found = false; for (size_t i = 0; i < count; i++) { if (FDs[i].fd == fd) { FDs[i].events |= events; found = true; @@ -102,12 +110,19 @@ - (void)of_removeObject: (id)object fileDescriptor: (int)fd events: (short)events { - struct pollfd *FDs = _FDs.mutableItems; - size_t nFDs = _FDs.count; + struct pollfd *FDs; + size_t nFDs; + + if (fd < 0) + @throw [OFObserveFailedException exceptionWithObserver: self + errNo: EBADF]; + + FDs = _FDs.mutableItems; + nFDs = _FDs.count; for (size_t i = 0; i < nFDs; i++) { if (FDs[i].fd == fd) { FDs[i].events &= ~events; @@ -160,18 +175,21 @@ [super removeObjectForWriting: object]; } - (void)observeForTimeInterval: (of_time_interval_t)timeInterval { + void *pool; struct pollfd *FDs; int events; size_t nFDs; if ([self of_processReadBuffers]) return; - FDs = _FDs.mutableItems; + pool = objc_autoreleasePoolPush(); + + FDs = [[[_FDs mutableCopy] autorelease] mutableItems]; nFDs = _FDs.count; #ifdef OPEN_MAX if (nFDs > OPEN_MAX) @throw [OFOutOfRangeException exception]; @@ -186,11 +204,11 @@ for (size_t i = 0; i < nFDs; i++) { assert(FDs[i].fd <= _maxFD); if (FDs[i].revents & POLLIN) { - void *pool; + void *pool2; if (FDs[i].fd == _cancelFD[0]) { char buffer; #ifdef OF_HAVE_PIPE @@ -202,30 +220,32 @@ FDs[i].revents = 0; continue; } - pool = objc_autoreleasePoolPush(); + pool2 = objc_autoreleasePoolPush(); if ([_delegate respondsToSelector: @selector(objectIsReadyForReading:)]) [_delegate objectIsReadyForReading: _FDToObject[FDs[i].fd]]; - objc_autoreleasePoolPop(pool); + objc_autoreleasePoolPop(pool2); } if (FDs[i].revents & (POLLOUT | POLLHUP)) { - void *pool = objc_autoreleasePoolPush(); + void *pool2 = objc_autoreleasePoolPush(); if ([_delegate respondsToSelector: @selector(objectIsReadyForWriting:)]) [_delegate objectIsReadyForWriting: _FDToObject[FDs[i].fd]]; - objc_autoreleasePoolPop(pool); + objc_autoreleasePoolPop(pool2); } FDs[i].revents = 0; } + + objc_autoreleasePoolPop(pool); } @end Index: src/OFProcess.m ================================================================== --- src/OFProcess.m +++ src/OFProcess.m @@ -15,578 +15,12 @@ * file. */ #include "config.h" -#include -#include - -#include - -#ifdef HAVE_SYS_WAIT_H -# include -#endif - -#include "unistd_wrapper.h" -#ifdef HAVE_SPAWN_H -# include -#endif - -#import "OFProcess.h" -#import "OFString.h" -#import "OFArray.h" -#import "OFDictionary.h" -#import "OFData.h" -#import "OFLocale.h" - -#import "OFInitializationFailedException.h" -#import "OFNotOpenException.h" -#import "OFOutOfRangeException.h" -#import "OFReadFailedException.h" -#import "OFWriteFailedException.h" +#include "platform.h" #ifdef OF_WINDOWS -# include -#endif - -#if !defined(OF_WINDOWS) && !defined(HAVE_POSIX_SPAWNP) -extern char **environ; -#endif - -@interface OFProcess () -#ifndef OF_WINDOWS -- (void)of_getArgv: (char ***)argv - forProgramName: (OFString *)programName - andArguments: (OFArray *)arguments; -- (char **)of_environmentForDictionary: (OFDictionary *)dictionary; -#else -- (of_char16_t *)of_environmentForDictionary: (OFDictionary *)dictionary; -#endif -@end - -@implementation OFProcess -+ (instancetype)processWithProgram: (OFString *)program -{ - return [[[self alloc] initWithProgram: program] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - arguments: (OFArray *)arguments -{ - return [[[self alloc] initWithProgram: program - arguments: arguments] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments -{ - return [[[self alloc] initWithProgram: program - programName: programName - arguments: arguments] autorelease]; -} - -+ (instancetype)processWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments - environment: (OFDictionary *)environment -{ - return [[[self alloc] initWithProgram: program - programName: programName - arguments: arguments - environment: environment] autorelease]; -} - -- (instancetype)init -{ - OF_INVALID_INIT_METHOD -} - -- (instancetype)initWithProgram: (OFString *)program -{ - return [self initWithProgram: program - programName: program - arguments: nil - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - arguments: (OFArray *)arguments -{ - return [self initWithProgram: program - programName: program - arguments: arguments - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments -{ - return [self initWithProgram: program - programName: program - arguments: arguments - environment: nil]; -} - -- (instancetype)initWithProgram: (OFString *)program - programName: (OFString *)programName - arguments: (OFArray *)arguments - environment: (OFDictionary *)environment -{ - self = [super init]; - - @try { -#ifndef OF_WINDOWS - void *pool = objc_autoreleasePoolPush(); - const char *path; - char **argv; - - if (pipe(_readPipe) != 0 || pipe(_writePipe) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - path = [program cStringWithEncoding: [OFLocale encoding]]; - [self of_getArgv: &argv - forProgramName: programName - andArguments: arguments]; - - @try { - char **env = [self - of_environmentForDictionary: environment]; -# ifdef HAVE_POSIX_SPAWNP - posix_spawn_file_actions_t actions; - posix_spawnattr_t attr; - - if (posix_spawn_file_actions_init(&actions) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (posix_spawnattr_init(&attr) != 0) { - posix_spawn_file_actions_destroy(&actions); - - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } - - @try { - if (posix_spawn_file_actions_addclose(&actions, - _readPipe[0]) != 0 || - posix_spawn_file_actions_addclose(&actions, - _writePipe[1]) != 0 || - posix_spawn_file_actions_adddup2(&actions, - _writePipe[0], 0) != 0 || - posix_spawn_file_actions_adddup2(&actions, - _readPipe[1], 1) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - -# ifdef POSIX_SPAWN_CLOEXEC_DEFAULT - if (posix_spawnattr_setflags(&attr, - POSIX_SPAWN_CLOEXEC_DEFAULT) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; -# endif - - if (posix_spawnp(&_pid, path, &actions, &attr, - argv, env) != 0) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } @finally { - posix_spawn_file_actions_destroy(&actions); - posix_spawnattr_destroy(&attr); - } -# else - if ((_pid = vfork()) == 0) { - environ = env; - - close(_readPipe[0]); - close(_writePipe[1]); - dup2(_writePipe[0], 0); - dup2(_readPipe[1], 1); - execvp(path, argv); - - _exit(EXIT_FAILURE); - } - - if (_pid == -1) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; -# endif - } @finally { - close(_readPipe[1]); - close(_writePipe[0]); - [self freeMemory: argv]; - } - - objc_autoreleasePoolPop(pool); -#else - SECURITY_ATTRIBUTES sa; - PROCESS_INFORMATION pi; - STARTUPINFOW si; - void *pool; - OFMutableString *argumentsString; - of_char16_t *argumentsCopy; - size_t length; - - sa.nLength = sizeof(sa); - sa.bInheritHandle = TRUE; - sa.lpSecurityDescriptor = NULL; - - if (!CreatePipe(&_readPipe[0], &_readPipe[1], &sa, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (!SetHandleInformation(_readPipe[0], HANDLE_FLAG_INHERIT, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (!CreatePipe(&_writePipe[0], &_writePipe[1], &sa, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - if (!SetHandleInformation(_writePipe[1], - HANDLE_FLAG_INHERIT, 0)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - - memset(&pi, 0, sizeof(pi)); - memset(&si, 0, sizeof(si)); - - si.cb = sizeof(si); - si.hStdInput = _writePipe[0]; - si.hStdOutput = _readPipe[1]; - si.hStdError = GetStdHandle(STD_ERROR_HANDLE); - si.dwFlags |= STARTF_USESTDHANDLES; - - pool = objc_autoreleasePoolPush(); - - argumentsString = - [OFMutableString stringWithString: programName]; - [argumentsString replaceOccurrencesOfString: @"\\\"" - withString: @"\\\\\""]; - [argumentsString replaceOccurrencesOfString: @"\"" - withString: @"\\\""]; - - if ([argumentsString containsString: @" "]) { - [argumentsString prependString: @"\""]; - [argumentsString appendString: @"\""]; - } - - for (OFString *argument in arguments) { - OFMutableString *tmp = - [[argument mutableCopy] autorelease]; - bool containsSpaces = [tmp containsString: @" "]; - - [argumentsString appendString: @" "]; - - if (containsSpaces) - [argumentsString appendString: @"\""]; - - [tmp replaceOccurrencesOfString: @"\\\"" - withString: @"\\\\\""]; - [tmp replaceOccurrencesOfString: @"\"" - withString: @"\\\""]; - - [argumentsString appendString: tmp]; - - if (containsSpaces) - [argumentsString appendString: @"\""]; - } - - length = argumentsString.UTF16StringLength; - argumentsCopy = [self allocMemoryWithSize: sizeof(of_char16_t) - count: length + 1]; - memcpy(argumentsCopy, argumentsString.UTF16String, - (argumentsString.UTF16StringLength + 1) * 2); - @try { - if (!CreateProcessW(program.UTF16String, - argumentsCopy, NULL, NULL, TRUE, - CREATE_UNICODE_ENVIRONMENT, - [self of_environmentForDictionary: environment], - NULL, &si, &pi)) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; - } @finally { - [self freeMemory: argumentsCopy]; - } - - objc_autoreleasePoolPop(pool); - - _process = pi.hProcess; - CloseHandle(pi.hThread); - - CloseHandle(_readPipe[1]); - CloseHandle(_writePipe[0]); -#endif - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - [self close]; - - [super dealloc]; -} - -#ifndef OF_WINDOWS -- (void)of_getArgv: (char ***)argv - forProgramName: (OFString *)programName - andArguments: (OFArray *)arguments -{ - OFString *const *objects = arguments.objects; - size_t i, count = arguments.count; - of_string_encoding_t encoding; - - *argv = [self allocMemoryWithSize: sizeof(char *) - count: count + 2]; - - encoding = [OFLocale encoding]; - - (*argv)[0] = (char *)[programName cStringWithEncoding: encoding]; - - for (i = 0; i < count; i++) - (*argv)[i + 1] = - (char *)[objects[i] cStringWithEncoding: encoding]; - - (*argv)[i + 1] = NULL; -} - -- (char **)of_environmentForDictionary: (OFDictionary *)environment -{ - OFEnumerator *keyEnumerator, *objectEnumerator; - char **envp; - size_t i, count; - of_string_encoding_t encoding; - - if (environment == nil) - return NULL; - - encoding = [OFLocale encoding]; - - count = environment.count; - envp = [self allocMemoryWithSize: sizeof(char *) - count: count + 1]; - - keyEnumerator = [environment keyEnumerator]; - objectEnumerator = [environment objectEnumerator]; - - for (i = 0; i < count; i++) { - OFString *key; - OFString *object; - size_t keyLen, objectLen; - - key = [keyEnumerator nextObject]; - object = [objectEnumerator nextObject]; - - keyLen = [key cStringLengthWithEncoding: encoding]; - objectLen = [object cStringLengthWithEncoding: encoding]; - - envp[i] = [self allocMemoryWithSize: keyLen + objectLen + 2]; - - memcpy(envp[i], [key cStringWithEncoding: encoding], keyLen); - envp[i][keyLen] = '='; - memcpy(envp[i] + keyLen + 1, - [object cStringWithEncoding: encoding], objectLen); - envp[i][keyLen + objectLen + 1] = '\0'; - } - - envp[i] = NULL; - - return envp; -} -#else -- (of_char16_t *)of_environmentForDictionary: (OFDictionary *)environment -{ - OFMutableData *env; - OFEnumerator *keyEnumerator, *objectEnumerator; - OFString *key, *object; - const of_char16_t equal = '='; - const of_char16_t zero[2] = { 0, 0 }; - - if (environment == nil) - return NULL; - - env = [OFMutableData dataWithItemSize: sizeof(of_char16_t)]; - - keyEnumerator = [environment keyEnumerator]; - objectEnumerator = [environment objectEnumerator]; - while ((key = [keyEnumerator nextObject]) != nil && - (object = [objectEnumerator nextObject]) != nil) { - [env addItems: key.UTF16String - count: key.UTF16StringLength]; - [env addItems: &equal - count: 1]; - [env addItems: object.UTF16String - count: object.UTF16StringLength]; - [env addItems: &zero - count: 1]; - } - [env addItems: zero - count: 2]; - - return env.mutableItems; -} -#endif - -- (bool)lowlevelIsAtEndOfStream -{ -#ifndef OF_WINDOWS - if (_readPipe[0] == -1) -#else - if (_readPipe[0] == NULL) -#endif - @throw [OFNotOpenException exceptionWithObject: self]; - - return _atEndOfStream; -} - -- (size_t)lowlevelReadIntoBuffer: (void *)buffer - length: (size_t)length -{ -#ifndef OF_WINDOWS - ssize_t ret; - - if (_readPipe[0] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - if ((ret = read(_readPipe[0], buffer, length)) < 0) - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: length - errNo: errno]; -#else - DWORD ret; - - if (length > UINT32_MAX) - @throw [OFOutOfRangeException exception]; - - if (_readPipe[0] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (!ReadFile(_readPipe[0], buffer, (DWORD)length, &ret, NULL)) { - if (GetLastError() == ERROR_BROKEN_PIPE) { - _atEndOfStream = true; - return 0; - } - - @throw [OFReadFailedException exceptionWithObject: self - requestedLength: length - errNo: EIO]; - } -#endif - - if (ret == 0) - _atEndOfStream = true; - - return ret; -} - -- (size_t)lowlevelWriteBuffer: (const void *)buffer - length: (size_t)length -{ -#ifndef OF_WINDOWS - ssize_t bytesWritten; - - if (_writePipe[1] == -1) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (length > SSIZE_MAX) - @throw [OFOutOfRangeException exception]; - - if ((bytesWritten = write(_writePipe[1], buffer, length)) < 0) - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: errno]; -#else - DWORD bytesWritten; - - if (length > UINT32_MAX) - @throw [OFOutOfRangeException exception]; - - if (_writePipe[1] == NULL) - @throw [OFNotOpenException exceptionWithObject: self]; - - if (!WriteFile(_writePipe[1], buffer, (DWORD)length, &bytesWritten, - NULL)) { - int errNo = EIO; - - if (GetLastError() == ERROR_BROKEN_PIPE) - errNo = EPIPE; - - @throw [OFWriteFailedException exceptionWithObject: self - requestedLength: length - bytesWritten: 0 - errNo: errNo]; - } -#endif - - return (size_t)bytesWritten; -} - -#ifndef OF_WINDOWS -- (int)fileDescriptorForReading -{ - return _readPipe[0]; -} - -- (int)fileDescriptorForWriting -{ - return _writePipe[1]; -} -#endif - -- (void)closeForWriting -{ -#ifndef OF_WINDOWS - if (_writePipe[1] != -1) - close(_writePipe[1]); - - _writePipe[1] = -1; -#else - if (_writePipe[1] != NULL) - CloseHandle(_writePipe[1]); - - _writePipe[1] = NULL; -#endif -} - -- (void)close -{ -#ifndef OF_WINDOWS - if (_readPipe[0] != -1) - close(_readPipe[0]); - if (_writePipe[1] != -1) - close(_writePipe[1]); - - if (_pid != -1) { - kill(_pid, SIGTERM); - waitpid(_pid, &_status, WNOHANG); - } - - _pid = -1; - _readPipe[0] = -1; - _writePipe[1] = -1; -#else - if (_readPipe[0] != NULL) - CloseHandle(_readPipe[0]); - if (_writePipe[1] != NULL) - CloseHandle(_writePipe[1]); - - if (_process != INVALID_HANDLE_VALUE) { - TerminateProcess(_process, 0); - CloseHandle(_process); - } - - _process = INVALID_HANDLE_VALUE; - _readPipe[0] = NULL; - _writePipe[1] = NULL; -#endif - - [super close]; -} -@end +# include "platform/windows/OFProcess.m" +#else +# include "platform/posix/OFProcess.m" +#endif Index: src/OFSelectKernelEventObserver.m ================================================================== --- src/OFSelectKernelEventObserver.m +++ src/OFSelectKernelEventObserver.m @@ -47,37 +47,46 @@ @implementation OFSelectKernelEventObserver - (instancetype)init { self = [super init]; + @try { #ifdef OF_AMIGAOS - _maxFD = 0; + _maxFD = 0; #else # ifndef OF_WINDOWS - if (_cancelFD[0] >= (int)FD_SETSIZE) - @throw [OFInitializationFailedException - exceptionWithClass: self.class]; + if (_cancelFD[0] >= (int)FD_SETSIZE) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; # endif - FD_ZERO(&_readFDs); - FD_ZERO(&_writeFDs); - FD_SET(_cancelFD[0], &_readFDs); - - if (_cancelFD[0] > INT_MAX) - @throw [OFOutOfRangeException exception]; - - _maxFD = (int)_cancelFD[0]; + FD_ZERO(&_readFDs); + FD_ZERO(&_writeFDs); + FD_SET(_cancelFD[0], &_readFDs); + + if (_cancelFD[0] > INT_MAX) + @throw [OFOutOfRangeException exception]; + + _maxFD = (int)_cancelFD[0]; #endif + } @catch (id e) { + [self release]; + @throw e; + } return self; } - (void)addObjectForReading: (id )object { int fd = object.fileDescriptorForReading; - if (fd < 0 || fd > INT_MAX - 1) + if (fd < 0) + @throw [OFObserveFailedException exceptionWithObserver: self + errNo: EBADF]; + + if (fd > INT_MAX - 1) @throw [OFOutOfRangeException exception]; #ifndef OF_WINDOWS if (fd >= (int)FD_SETSIZE) @throw [OFOutOfRangeException exception]; @@ -93,11 +102,15 @@ - (void)addObjectForWriting: (id )object { int fd = object.fileDescriptorForWriting; - if (fd < 0 || fd > INT_MAX - 1) + if (fd < 0) + @throw [OFObserveFailedException exceptionWithObserver: self + errNo: EBADF]; + + if (fd > INT_MAX - 1) @throw [OFOutOfRangeException exception]; #ifndef OF_WINDOWS if (fd >= (int)FD_SETSIZE) @throw [OFOutOfRangeException exception]; @@ -116,11 +129,12 @@ /* TODO: Adjust _maxFD */ int fd = object.fileDescriptorForReading; if (fd < 0) - @throw [OFOutOfRangeException exception]; + @throw [OFObserveFailedException exceptionWithObserver: self + errNo: EBADF]; #ifndef OF_WINDOWS if (fd >= (int)FD_SETSIZE) @throw [OFOutOfRangeException exception]; #endif @@ -135,11 +149,13 @@ /* TODO: Adjust _maxFD */ int fd = object.fileDescriptorForWriting; if (fd < 0) - @throw [OFOutOfRangeException exception]; + @throw [OFObserveFailedException exceptionWithObserver: self + errNo: EBADF]; + #ifndef OF_WINDOWS if (fd >= (int)FD_SETSIZE) @throw [OFOutOfRangeException exception]; #endif Index: src/OFStdIOStream.m ================================================================== --- src/OFStdIOStream.m +++ src/OFStdIOStream.m @@ -176,11 +176,16 @@ } #endif - (void)dealloc { - [self close]; +#ifndef OF_AMIGAOS + if (_fd != -1) +#else + if (_handle != 0) +#endif + [self close]; [super dealloc]; } - (bool)lowlevelIsAtEndOfStream @@ -299,16 +304,20 @@ #endif - (void)close { #ifndef OF_AMIGAOS - if (_fd != -1) - close(_fd); + if (_fd == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + close(_fd); _fd = -1; #else - if (_closable && _handle != 0) + if (_handle == 0) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (_closable) Close(_handle); _handle = 0; #endif Index: src/OFStream.m ================================================================== --- src/OFStream.m +++ src/OFStream.m @@ -1907,14 +1907,10 @@ _readBufferLength += length; } - (void)close { -#ifdef OF_HAVE_SOCKETS - [self cancelAsyncRequests]; -#endif - [self freeMemory: _readBufferMemory]; _readBuffer = _readBufferMemory = NULL; _readBufferLength = 0; [self freeMemory: _writeBuffer]; Index: src/OFString+PathAdditions.h ================================================================== --- src/OFString+PathAdditions.h +++ src/OFString+PathAdditions.h @@ -82,8 +82,15 @@ * * @param component The path component to append * @return A new, autoreleased OFString with the path component appended */ - (OFString *)stringByAppendingPathComponent: (OFString *)component; + +- (bool)of_isDirectoryPath; +- (OFString *)of_pathToURLPathWithURLEncodedHost: + (OFString *__autoreleasing _Nullable *_Nonnull)URLEncodedHost; +- (OFString *)of_URLPathToPathWithURLEncodedHost: + (nullable OFString *)URLEncodedHost; +- (OFString *)of_pathComponentToURLPathComponent; @end OF_ASSUME_NONNULL_END Index: src/OFString+PathAdditions.m ================================================================== --- src/OFString+PathAdditions.m +++ src/OFString+PathAdditions.m @@ -15,16 +15,16 @@ * file. */ #include "config.h" -#import "platform.h" +#include "platform.h" #if defined(OF_WINDOWS) || defined(OF_MSDOS) -# import "OFString+PathAdditions_DOS.m" +# import "platform/windows/OFString+PathAdditions.m" #elif defined(OF_AMIGAOS) -# import "OFString+PathAdditions_AmigaOS.m" +# import "platform/amiga/OFString+PathAdditions.m" #elif defined(OF_NINTENDO_3DS) || defined(OF_WII) -# import "OFString+PathAdditions_libfat.m" +# import "platform/libfat/OFString+PathAdditions.m" #else -# import "OFString+PathAdditions_UNIX.m" +# import "platform/posix/OFString+PathAdditions.m" #endif DELETED src/OFString+PathAdditions_AmigaOS.m Index: src/OFString+PathAdditions_AmigaOS.m ================================================================== --- src/OFString+PathAdditions_AmigaOS.m +++ src/OFString+PathAdditions_AmigaOS.m @@ -1,273 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#import "OFString+PathAdditions.h" -#import "OFArray.h" - -#import "OFOutOfRangeException.h" - -int _OFString_PathAdditions_reference; - -@implementation OFString (PathAdditions) -+ (OFString *)pathWithComponents: (OFArray *)components -{ - OFMutableString *ret = [OFMutableString string]; - void *pool = objc_autoreleasePoolPush(); - bool firstAfterDevice = true; - - for (OFString *component in components) { - if (component.length == 0) - continue; - - if (!firstAfterDevice) - [ret appendString: @"/"]; - - [ret appendString: component]; - - if (![component hasSuffix: @":"]) - firstAfterDevice = false; - } - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (bool)isAbsolutePath -{ - return [self containsString: @":"]; -} - -- (OFArray *)pathComponents -{ - OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; - void *pool = objc_autoreleasePoolPush(); - const char *cString = self.UTF8String; - size_t i, last = 0, cStringLength = self.UTF8StringLength; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return ret; - } - - for (i = 0; i < cStringLength; i++) { - if (cString[i] == '/') { - if (i - last != 0) - [ret addObject: [OFString - stringWithUTF8String: cString + last - length: i - last]]; - else - [ret addObject: @"/"]; - - last = i + 1; - } else if (cString[i] == ':') { - [ret addObject: [OFString - stringWithUTF8String: cString + last - length: i - last + 1]]; - - last = i + 1; - } - } - if (i - last != 0) - [ret addObject: [OFString stringWithUTF8String: cString + last - length: i - last]]; - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (OFString *)lastPathComponent -{ - /* - * AmigaOS needs the full parsing to determine the last path component. - * This could be optimized by not creating the temporary objects, - * though. - */ - void *pool = objc_autoreleasePoolPush(); - OFString *ret = self.pathComponents.lastObject; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)pathExtension -{ - void *pool = objc_autoreleasePoolPush(); - OFString *ret, *fileName; - size_t pos; - - fileName = self.lastPathComponent; - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - ret = [fileName substringWithRange: - of_range(pos + 1, fileName.length - pos - 1)]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByDeletingLastPathComponent -{ - /* - * AmigaOS needs the full parsing to delete the last path component. - * This could be optimized, though. - */ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components = self.pathComponents; - size_t count = components.count; - OFString *ret; - - if (count < 2) { - if ([components.firstObject hasSuffix: @":"]) { - ret = [components.firstObject retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; - } - - objc_autoreleasePoolPop(pool); - return @""; - } - - components = [components objectsInRange: - of_range(0, components.count - 1)]; - ret = [OFString pathWithComponents: components]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByDeletingPathExtension -{ - void *pool; - OFMutableArray OF_GENERIC(OFString *) *components; - OFString *ret, *fileName; - size_t pos; - - if (self.length == 0) - return [[self copy] autorelease]; - - pool = objc_autoreleasePoolPush(); - components = [[self.pathComponents mutableCopy] autorelease]; - fileName = components.lastObject; - - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - fileName = [fileName substringWithRange: of_range(0, pos)]; - [components replaceObjectAtIndex: components.count - 1 - withObject: fileName]; - - ret = [OFString pathWithComponents: components]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByStandardizingPath -{ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components; - OFMutableArray OF_GENERIC(OFString *) *array; - OFString *ret; - bool done = false; - - if (self.length == 0) - return @""; - - components = self.pathComponents; - - if (components.count == 1) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - array = [[components mutableCopy] autorelease]; - - while (!done) { - size_t length = array.count; - - done = true; - - for (size_t i = 0; i < length; i++) { - OFString *component = [array objectAtIndex: i]; - OFString *parent = - (i > 0 ? [array objectAtIndex: i - 1] : 0); - - if (component.length == 0) { - [array removeObjectAtIndex: i]; - - done = false; - break; - } - - if ([component isEqual: @"/"] && - parent != nil && ![parent isEqual: @"/"]) { - [array removeObjectsInRange: - of_range(i - 1, 2)]; - - done = false; - break; - } - } - } - - ret = [OFString pathWithComponents: array]; - - if ([self hasSuffix: @"/"]) - ret = [ret stringByAppendingString: @"/"]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByAppendingPathComponent: (OFString *)component -{ - if ([self hasSuffix: @"/"] || [self hasSuffix: @":"]) - return [self stringByAppendingString: component]; - else { - OFMutableString *ret = [[self mutableCopy] autorelease]; - - [ret appendString: @"/"]; - [ret appendString: component]; - - [ret makeImmutable]; - - return ret; - } -} -@end DELETED src/OFString+PathAdditions_DOS.m Index: src/OFString+PathAdditions_DOS.m ================================================================== --- src/OFString+PathAdditions_DOS.m +++ src/OFString+PathAdditions_DOS.m @@ -1,314 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#import "OFString+PathAdditions.h" -#import "OFArray.h" - -#import "OFOutOfRangeException.h" - -int _OFString_PathAdditions_reference; - -@implementation OFString (PathAdditions) -+ (OFString *)pathWithComponents: (OFArray *)components -{ - OFMutableString *ret = [OFMutableString string]; - void *pool = objc_autoreleasePoolPush(); - bool first = true; - - for (OFString *component in components) { - if (component.length == 0) - continue; - - if (!first && ![ret hasSuffix: @":"] && - ([component isEqual: @"\\"] || [component isEqual: @"/"])) - continue; - - if (!first && ![ret hasSuffix: @"\\"] && - ![ret hasSuffix: @"/"] && ![ret hasSuffix: @":"]) - [ret appendString: @"\\"]; - - [ret appendString: component]; - - first = false; - } - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (bool)isAbsolutePath -{ -#ifdef OF_WINDOWS - if ([self hasPrefix: @"\\\\"]) - return true; -#endif - - return ([self containsString: @":\\"] || [self containsString: @":/"]); -} - -- (OFArray *)pathComponents -{ - OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; - void *pool = objc_autoreleasePoolPush(); - const char *cString = self.UTF8String; - size_t i, last = 0, cStringLength = self.UTF8StringLength; - bool isUNC = false; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return ret; - } - - if ([self hasPrefix: @"\\\\"]) { - isUNC = true; - [ret addObject: @"\\\\"]; - - cString += 2; - cStringLength -= 2; - } - - for (i = 0; i < cStringLength; i++) { - if (cString[i] == '\\' || cString[i] == '/') { - if (i == 0) - [ret addObject: [OFString - stringWithUTF8String: cString - length: 1]]; - else if (i - last != 0) - [ret addObject: [OFString - stringWithUTF8String: cString + last - length: i - last]]; - - last = i + 1; - } else if (!isUNC && cString[i] == ':') { - if (i + 1 < cStringLength && - (cString[i + 1] == '\\' || cString[i + 1] == '/')) - i++; - - [ret addObject: [OFString - stringWithUTF8String: cString + last - length: i - last + 1]]; - - last = i + 1; - } - } - if (i - last != 0) - [ret addObject: [OFString stringWithUTF8String: cString + last - length: i - last]]; - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (OFString *)lastPathComponent -{ - /* - * Windows/DOS need the full parsing to determine the last path - * component. This could be optimized by not creating the temporary - * objects, though. - */ - void *pool = objc_autoreleasePoolPush(); - OFString *ret = self.pathComponents.lastObject; - - if (ret == nil) { - objc_autoreleasePoolPop(pool); - return @""; - } - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)pathExtension -{ - void *pool = objc_autoreleasePoolPush(); - OFString *ret, *fileName; - size_t pos; - - fileName = self.lastPathComponent; - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - ret = [fileName substringWithRange: - of_range(pos + 1, fileName.length - pos - 1)]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByDeletingLastPathComponent -{ - /* - * Windows/DOS need the full parsing to delete the last path component. - * This could be optimized, though. - */ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components = self.pathComponents; - size_t count = components.count; - OFString *ret; - - if (count == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (count == 1) { - OFString *firstComponent = components.firstObject; - - if ([firstComponent hasSuffix: @":"] || - [firstComponent hasSuffix: @":\\"] || - [firstComponent hasSuffix: @":/"] || - [firstComponent hasPrefix: @"\\"]) { - ret = [firstComponent retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; - } - - objc_autoreleasePoolPop(pool); - return @"."; - } - - components = [components objectsInRange: - of_range(0, components.count - 1)]; - ret = [OFString pathWithComponents: components]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByDeletingPathExtension -{ - void *pool; - OFMutableArray OF_GENERIC(OFString *) *components; - OFString *ret, *fileName; - size_t pos; - - if (self.length == 0) - return [[self copy] autorelease]; - - pool = objc_autoreleasePoolPush(); - components = [[self.pathComponents mutableCopy] autorelease]; - fileName = components.lastObject; - - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - fileName = [fileName substringWithRange: of_range(0, pos)]; - [components replaceObjectAtIndex: components.count - 1 - withObject: fileName]; - - ret = [OFString pathWithComponents: components]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByStandardizingPath -{ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components; - OFMutableArray OF_GENERIC(OFString *) *array; - OFString *ret; - bool done = false; - - if (self.length == 0) - return @""; - - components = self.pathComponents; - - if (components.count == 1) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - array = [[components mutableCopy] autorelease]; - - while (!done) { - size_t length = array.count; - - done = true; - - for (size_t i = 0; i < length; i++) { - OFString *component = [array objectAtIndex: i]; - OFString *parent = - (i > 0 ? [array objectAtIndex: i - 1] : 0); - - if ([component isEqual: @"."] || - component.length == 0) { - [array removeObjectAtIndex: i]; - - done = false; - break; - } - - if ([component isEqual: @".."] && parent != nil && - ![parent isEqual: @".."] && - ![parent hasSuffix: @":"] && - ![parent hasSuffix: @":\\"] && - ![parent hasSuffix: @"://"] && - (![parent hasPrefix: @"\\"] || i != 1)) { - [array removeObjectsInRange: - of_range(i - 1, 2)]; - - done = false; - break; - } - } - } - - ret = [[OFString pathWithComponents: array] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; -} - -- (OFString *)stringByAppendingPathComponent: (OFString *)component -{ - if ([self hasSuffix: @"\\"] || [self hasSuffix: @"/"]) - return [self stringByAppendingString: component]; - else { - OFMutableString *ret = [[self mutableCopy] autorelease]; - - [ret appendString: @"\\"]; - [ret appendString: component]; - - [ret makeImmutable]; - - return ret; - } -} -@end DELETED src/OFString+PathAdditions_UNIX.m Index: src/OFString+PathAdditions_UNIX.m ================================================================== --- src/OFString+PathAdditions_UNIX.m +++ src/OFString+PathAdditions_UNIX.m @@ -1,319 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#import "OFString+PathAdditions.h" -#import "OFArray.h" - -#import "OFOutOfRangeException.h" - -int _OFString_PathAdditions_reference; - -@implementation OFString (PathAdditions) -+ (OFString *)pathWithComponents: (OFArray *)components -{ - OFMutableString *ret = [OFMutableString string]; - void *pool = objc_autoreleasePoolPush(); - bool first = true; - - for (OFString *component in components) { - if (component.length == 0) - continue; - - if (!first && [component isEqual: @"/"]) - continue; - - if (!first && ![ret hasSuffix: @"/"]) - [ret appendString: @"/"]; - - [ret appendString: component]; - - first = false; - } - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (bool)isAbsolutePath -{ - return [self hasPrefix: @"/"]; -} - -- (OFArray *)pathComponents -{ - OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; - void *pool = objc_autoreleasePoolPush(); - const char *cString = self.UTF8String; - size_t i, last = 0, cStringLength = self.UTF8StringLength; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return ret; - } - - for (i = 0; i < cStringLength; i++) { - if (cString[i] == '/') { - if (i == 0) - [ret addObject: @"/"]; - else if (i - last != 0) - [ret addObject: [OFString - stringWithUTF8String: cString + last - length: i - last]]; - - last = i + 1; - } - } - if (i - last != 0) - [ret addObject: [OFString stringWithUTF8String: cString + last - length: i - last]]; - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (OFString *)lastPathComponent -{ - void *pool = objc_autoreleasePoolPush(); - const char *cString = self.UTF8String; - size_t cStringLength = self.UTF8StringLength; - ssize_t i; - OFString *ret; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cString[cStringLength - 1] == '/') - cStringLength--; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @"/"; - } - - if (cStringLength - 1 > SSIZE_MAX) - @throw [OFOutOfRangeException exception]; - - for (i = cStringLength - 1; i >= 0; i--) { - if (cString[i] == '/') { - i++; - break; - } - } - - /* - * Only one component, but the trailing delimiter might have been - * removed, so return a new string anyway. - */ - if (i < 0) - i = 0; - - ret = [[OFString alloc] initWithUTF8String: cString + i - length: cStringLength - i]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; -} - -- (OFString *)pathExtension -{ - void *pool = objc_autoreleasePoolPush(); - OFString *ret, *fileName; - size_t pos; - - fileName = self.lastPathComponent; - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - ret = [fileName substringWithRange: - of_range(pos + 1, fileName.length - pos - 1)]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByDeletingLastPathComponent -{ - void *pool = objc_autoreleasePoolPush(); - const char *cString = self.UTF8String; - size_t cStringLength = self.UTF8StringLength; - OFString *ret; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cString[cStringLength - 1] == '/') - cStringLength--; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @"/"; - } - - for (size_t i = cStringLength; i >= 1; i--) { - if (cString[i - 1] == '/') { - if (i == 1) { - objc_autoreleasePoolPop(pool); - return @"/"; - } - - ret = [[OFString alloc] initWithUTF8String: cString - length: i - 1]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; - } - } - - objc_autoreleasePoolPop(pool); - - return @"."; -} - -- (OFString *)stringByDeletingPathExtension -{ - void *pool; - OFMutableArray OF_GENERIC(OFString *) *components; - OFString *ret, *fileName; - size_t pos; - - if (self.length == 0) - return [[self copy] autorelease]; - - pool = objc_autoreleasePoolPush(); - components = [[self.pathComponents mutableCopy] autorelease]; - fileName = components.lastObject; - - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - fileName = [fileName substringWithRange: of_range(0, pos)]; - [components replaceObjectAtIndex: [components count] - 1 - withObject: fileName]; - - ret = [OFString pathWithComponents: components]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByStandardizingPath -{ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components; - OFMutableArray OF_GENERIC(OFString *) *array; - OFString *ret; - bool done = false, startsWithSlash; - - if (self.length == 0) - return @""; - - components = self.pathComponents; - - if (components.count == 1) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - array = [[components mutableCopy] autorelease]; - startsWithSlash = [self hasPrefix: @"/"]; - - if (startsWithSlash) - [array removeObjectAtIndex: 0]; - - while (!done) { - size_t length = array.count; - - done = true; - - for (size_t i = 0; i < length; i++) { - OFString *component = [array objectAtIndex: i]; - OFString *parent = - (i > 0 ? [array objectAtIndex: i - 1] : 0); - - if ([component isEqual: @"."] || - component.length == 0) { - [array removeObjectAtIndex: i]; - - done = false; - break; - } - - if ([component isEqual: @".."] && - parent != nil && ![parent isEqual: @".."]) { - [array removeObjectsInRange: - of_range(i - 1, 2)]; - - done = false; - break; - } - } - } - - if (startsWithSlash) - [array insertObject: @"" - atIndex: 0]; - - if ([self hasSuffix: @"/"]) - [array addObject: @""]; - - ret = [[array componentsJoinedByString: @"/"] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; -} - -- (OFString *)stringByAppendingPathComponent: (OFString *)component -{ - if ([self hasSuffix: @"/"]) - return [self stringByAppendingString: component]; - else { - OFMutableString *ret = [[self mutableCopy] autorelease]; - - [ret appendString: @"/"]; - [ret appendString: component]; - - [ret makeImmutable]; - - return ret; - } -} -@end DELETED src/OFString+PathAdditions_libfat.m Index: src/OFString+PathAdditions_libfat.m ================================================================== --- src/OFString+PathAdditions_libfat.m +++ src/OFString+PathAdditions_libfat.m @@ -1,319 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#import "OFString+PathAdditions.h" -#import "OFArray.h" - -#import "OFOutOfRangeException.h" - -int _OFString_PathAdditions_reference; - -@implementation OFString (PathAdditions) -+ (OFString *)pathWithComponents: (OFArray *)components -{ - OFMutableString *ret = [OFMutableString string]; - void *pool = objc_autoreleasePoolPush(); - bool first = true; - - for (OFString *component in components) { - if (component.length == 0) - continue; - - if ([component isEqual: @"/"]) - continue; - - if (!first && ![ret hasSuffix: @"/"]) - [ret appendString: @"/"]; - - [ret appendString: component]; - - first = false; - } - - if ([ret hasSuffix: @":"]) - [ret appendString: @"/"]; - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (bool)isAbsolutePath -{ - return [self containsString: @":/"]; -} - -- (OFArray *)pathComponents -{ - OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; - void *pool = objc_autoreleasePoolPush(); - const char *cString = self.UTF8String; - size_t i, last = 0, cStringLength = self.UTF8StringLength; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return ret; - } - - for (i = 0; i < cStringLength; i++) { - if (cString[i] == '/') { - if (i - last != 0) - [ret addObject: [OFString - stringWithUTF8String: cString + last - length: i - last]]; - - last = i + 1; - } - } - if (i - last != 0) - [ret addObject: [OFString stringWithUTF8String: cString + last - length: i - last]]; - - [ret makeImmutable]; - - objc_autoreleasePoolPop(pool); - - return ret; -} - -- (OFString *)lastPathComponent -{ - void *pool = objc_autoreleasePoolPush(); - const char *cString; - size_t cStringLength; - ssize_t i; - OFString *ret; - - if ([self hasSuffix: @":/"]) - return self; - - cString = self.UTF8String; - cStringLength = self.UTF8StringLength; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cString[cStringLength - 1] == '/') - cStringLength--; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cStringLength - 1 > SSIZE_MAX) - @throw [OFOutOfRangeException exception]; - - for (i = cStringLength - 1; i >= 0; i--) { - if (cString[i] == '/') { - i++; - break; - } - } - - /* - * Only one component, but the trailing delimiter might have been - * removed, so return a new string anyway. - */ - if (i < 0) - i = 0; - - ret = [[OFString alloc] initWithUTF8String: cString + i - length: cStringLength - i]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; -} - -- (OFString *)pathExtension -{ - void *pool = objc_autoreleasePoolPush(); - OFString *ret, *fileName; - size_t pos; - - fileName = self.lastPathComponent; - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - ret = [fileName substringWithRange: - of_range(pos + 1, fileName.length - pos - 1)]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByDeletingLastPathComponent -{ - void *pool = objc_autoreleasePoolPush(); - const char *cString; - size_t cStringLength; - OFString *ret; - - if ([self hasSuffix: @":/"]) - return self; - - cString = self.UTF8String; - cStringLength = self.UTF8StringLength; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - if (cString[cStringLength - 1] == '/') - cStringLength--; - - if (cStringLength == 0) { - objc_autoreleasePoolPop(pool); - return @""; - } - - for (size_t i = cStringLength; i >= 1; i--) { - if (cString[i - 1] == '/') { - ret = [[OFString alloc] initWithUTF8String: cString - length: i - 1]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; - } - } - - objc_autoreleasePoolPop(pool); - - return @"."; -} - -- (OFString *)stringByDeletingPathExtension -{ - void *pool; - OFMutableArray OF_GENERIC(OFString *) *components; - OFString *ret, *fileName; - size_t pos; - - if (self.length == 0) - return [[self copy] autorelease]; - - pool = objc_autoreleasePoolPush(); - components = [[self.pathComponents mutableCopy] autorelease]; - fileName = components.lastObject; - - pos = [fileName rangeOfString: @"." - options: OF_STRING_SEARCH_BACKWARDS].location; - if (pos == OF_NOT_FOUND || pos == 0) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - fileName = [fileName substringWithRange: of_range(0, pos)]; - [components replaceObjectAtIndex: components.count - 1 - withObject: fileName]; - - ret = [OFString pathWithComponents: components]; - - [ret retain]; - objc_autoreleasePoolPop(pool); - return [ret autorelease]; -} - -- (OFString *)stringByStandardizingPath -{ - void *pool = objc_autoreleasePoolPush(); - OFArray OF_GENERIC(OFString *) *components; - OFMutableArray OF_GENERIC(OFString *) *array; - OFString *ret; - bool done = false; - - if (self.length == 0) - return @""; - - components = self.pathComponents; - - if (components.count == 1) { - objc_autoreleasePoolPop(pool); - return [[self copy] autorelease]; - } - - array = [[components mutableCopy] autorelease]; - - while (!done) { - size_t length = array.count; - - done = true; - - for (size_t i = 0; i < length; i++) { - OFString *component = [array objectAtIndex: i]; - OFString *parent = - (i > 0 ? [array objectAtIndex: i - 1] : 0); - - if ([component isEqual: @"."] || - component.length == 0) { - [array removeObjectAtIndex: i]; - - done = false; - break; - } - - if ([component isEqual: @".."] && - parent != nil && ![parent isEqual: @".."]) { - [array removeObjectsInRange: - of_range(i - 1, 2)]; - - done = false; - break; - } - } - } - - if ([self hasSuffix: @"/"]) - [array addObject: @""]; - - ret = [[array componentsJoinedByString: @"/"] retain]; - - objc_autoreleasePoolPop(pool); - - return [ret autorelease]; -} - -- (OFString *)stringByAppendingPathComponent: (OFString *)component -{ - if ([self hasSuffix: @"/"]) - return [self stringByAppendingString: component]; - else { - OFMutableString *ret = [[self mutableCopy] autorelease]; - - [ret appendString: @"/"]; - [ret appendString: component]; - - [ret makeImmutable]; - - return ret; - } -} -@end Index: src/OFTarArchive.m ================================================================== --- src/OFTarArchive.m +++ src/OFTarArchive.m @@ -176,11 +176,15 @@ if (_mode != OF_TAR_ARCHIVE_MODE_READ) @throw [OFInvalidArgumentException exception]; [(OFTarArchiveFileReadStream *)_lastReturnedStream of_skip]; - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } [_lastReturnedStream release]; _lastReturnedStream = nil; if (_stream.atEndOfStream) return nil; @@ -235,11 +239,15 @@ _mode != OF_TAR_ARCHIVE_MODE_APPEND) @throw [OFInvalidArgumentException exception]; pool = objc_autoreleasePoolPush(); - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } [_lastReturnedStream release]; _lastReturnedStream = nil; [entry of_writeToStream: _stream encoding: _encoding]; @@ -257,11 +265,15 @@ - (void)close { if (_stream == nil) return; - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } [_lastReturnedStream release]; _lastReturnedStream = nil; if (_mode == OF_TAR_ARCHIVE_MODE_WRITE || _mode == OF_TAR_ARCHIVE_MODE_APPEND) { @@ -294,11 +306,12 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; [_entry release]; [super dealloc]; } @@ -352,10 +365,13 @@ .fileDescriptorForReading; } - (void)close { + if (_stream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + [self of_skip]; [_stream release]; _stream = nil; @@ -427,11 +443,12 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; [_entry release]; [super dealloc]; } @@ -474,17 +491,19 @@ .fileDescriptorForWriting; } - (void)close { + uint64_t remainder; + if (_stream == nil) - return; - - uint64_t remainder = 512 - _entry.size % 512; + @throw [OFNotOpenException exceptionWithObject: self]; if (_toWrite > 0) @throw [OFTruncatedDataException exception]; + + remainder = 512 - _entry.size % 512; if (remainder != 512) { bool wasWriteBuffered = _stream.writeBuffered; [_stream setWriteBuffered: true]; Index: src/OFThread.h ================================================================== --- src/OFThread.h +++ src/OFThread.h @@ -71,11 +71,13 @@ enum of_thread_running { OF_THREAD_NOT_RUNNING, OF_THREAD_RUNNING, OF_THREAD_WAITING_FOR_JOIN } _running; +# ifndef OF_OBJFW_RUNTIME void *_pool; +# endif # ifdef OF_HAVE_BLOCKS of_thread_block_t _Nullable _threadBlock; # endif jmp_buf _exitEnv; id _returnValue; Index: src/OFThread.m ================================================================== --- src/OFThread.m +++ src/OFThread.m @@ -105,11 +105,13 @@ if (!of_tlskey_set(threadSelfKey, thread)) @throw [OFInitializationFailedException exceptionWithClass: thread.class]; +#ifndef OF_OBJFW_RUNTIME thread->_pool = objc_autoreleasePoolPush(); +#endif name = thread.name; if (name != nil) of_thread_set_name( [name cStringWithEncoding: [OFLocale encoding]]); @@ -136,11 +138,15 @@ thread->_returnValue = [[thread main] retain]; } [thread handleTermination]; +#ifdef OF_OBJFW_RUNTIME + objc_autoreleasePoolPop((void *)(uintptr_t)-1); +#else objc_autoreleasePoolPop(thread->_pool); +#endif #if defined(OF_AMIGAOS) && defined(OF_HAVE_SOCKETS) if (thread.supportsSockets) of_socket_deinit(); #endif Index: src/OFURL.h ================================================================== --- src/OFURL.h +++ src/OFURL.h @@ -340,9 +340,17 @@ * * @return The characters allowed in the fragment part of a URL. */ + (OFCharacterSet *)URLFragmentAllowedCharacterSet; @end + +#ifdef __cplusplus +extern "C" { +#endif +extern bool of_url_is_ipv6_host(OFString *host); +#ifdef __cplusplus +} +#endif OF_ASSUME_NONNULL_END #import "OFMutableURL.h" Index: src/OFURL.m ================================================================== --- src/OFURL.m +++ src/OFURL.m @@ -58,98 +58,40 @@ @interface OFURLQueryOrFragmentAllowedCharacterSet: OFURLAllowedCharacterSetBase + (OFCharacterSet *)URLQueryOrFragmentAllowedCharacterSet; @end -#ifdef OF_HAVE_FILES -static OFString * -pathToURLPath(OFString *path) -{ -# if defined(OF_WINDOWS) || defined(OF_MSDOS) - path = [path stringByReplacingOccurrencesOfString: @"\\" - withString: @"/"]; - path = [path stringByPrependingString: @"/"]; - - return path; -# elif defined(OF_AMIGAOS) - OFArray OF_GENERIC(OFString *) *components = path.pathComponents; - OFMutableString *ret = [OFMutableString string]; - - for (OFString *component in components) { - if (component.length == 0) - continue; - - if ([component isEqual: @"/"]) - [ret appendString: @"/.."]; - else { - [ret appendString: @"/"]; - [ret appendString: component]; - } - } - - [ret makeImmutable]; - - return ret; -# elif defined(OF_NINTENDO_3DS) || defined(OF_WII) - return [path stringByPrependingString: @"/"]; -# else - return path; -# endif -} - -static OFString * -URLPathToPath(OFString *path) -{ -# if defined(OF_WINDOWS) || defined(OF_MSDOS) - path = [path substringWithRange: of_range(1, path.length - 1)]; - path = [path stringByReplacingOccurrencesOfString: @"/" - withString: @"\\"]; - - return path; -# elif defined(OF_AMIGAOS) - OFMutableArray OF_GENERIC(OFString *) *components; - size_t count; - - path = [path substringWithRange: of_range(1, path.length - 1)]; - components = [[[path - componentsSeparatedByString: @"/"] mutableCopy] autorelease]; - count = components.count; - - for (size_t i = 0; i < count; i++) { - OFString *component = [components objectAtIndex: i]; - - if ([component isEqual: @"."]) { - [components removeObjectAtIndex: i]; - count--; - - i--; - continue; - } - - if ([component isEqual: @".."]) - [components replaceObjectAtIndex: i - withObject: @"/"]; - } - - return [OFString pathWithComponents: components]; -# elif defined(OF_NINTENDO_3DS) || defined(OF_WII) - return [path substringWithRange: of_range(1, path.length - 1)]; -# else - return path; -# endif -} -#endif - @interface OFInvertedCharacterSetWithoutPercent: OFCharacterSet { OFCharacterSet *_characterSet; bool (*_characterIsMember)(id, SEL, of_unichar_t); } - (instancetype)of_initWithCharacterSet: (OFCharacterSet *)characterSet OF_METHOD_FAMILY(init); @end + +bool +of_url_is_ipv6_host(OFString *host) +{ + const char *UTF8String = host.UTF8String; + bool hasColon = false; + + while (*UTF8String != '\0') { + if (!of_ascii_isdigit(*UTF8String) && *UTF8String != ':' && + (*UTF8String < 'a' || *UTF8String > 'f') && + (*UTF8String < 'A' || *UTF8String > 'F')) + return false; + + if (*UTF8String == ':') + hasColon = true; + + UTF8String++; + } + + return hasColon; +} @implementation OFURLAllowedCharacterSetBase - (instancetype)init { OF_INVALID_INIT_METHOD @@ -469,10 +411,11 @@ self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); char *tmp, *tmp2; + bool isIPv6Host = false; if ((UTF8String2 = of_strdup(string.UTF8String)) == NULL) @throw [OFOutOfMemoryException exceptionWithRequestedSize: string.UTF8StringLength]; @@ -528,11 +471,57 @@ [OFCharacterSet URLUserAllowedCharacterSet]); UTF8String = tmp2; } - if ((tmp2 = strchr(UTF8String, ':')) != NULL) { + if (UTF8String[0] == '[') { + tmp2 = UTF8String++; + + while (of_ascii_isdigit(*UTF8String) || + *UTF8String == ':' || + (*UTF8String >= 'a' && *UTF8String <= 'f') || + (*UTF8String >= 'A' && *UTF8String <= 'F')) + UTF8String++; + + if (*UTF8String != ']') + @throw [OFInvalidFormatException exception]; + + UTF8String++; + + _URLEncodedHost = [[OFString alloc] + initWithUTF8String: tmp2 + length: UTF8String - tmp2]; + + if (*UTF8String == ':') { + OFString *portString; + + tmp2 = ++UTF8String; + + while (*UTF8String != '\0') { + if (!of_ascii_isdigit(*UTF8String)) + @throw [OFInvalidFormatException + exception]; + + UTF8String++; + } + + portString = [OFString + stringWithUTF8String: tmp2 + length: UTF8String - tmp2]; + + if (portString.length == 0 || + portString.decimalValue > 65535) + @throw [OFInvalidFormatException + exception]; + + _port = [[OFNumber alloc] initWithUInt16: + (uint16_t)portString.decimalValue]; + } else if (*UTF8String != '\0') + @throw [OFInvalidFormatException exception]; + + isIPv6Host = true; + } else if ((tmp2 = strchr(UTF8String, ':')) != NULL) { OFString *portString; *tmp2 = '\0'; tmp2++; @@ -548,12 +537,13 @@ (uint16_t)portString.decimalValue]; } else _URLEncodedHost = [[OFString alloc] initWithUTF8String: UTF8String]; - of_url_verify_escaped(_URLEncodedHost, - [OFCharacterSet URLHostAllowedCharacterSet]); + if (!isIPv6Host) + of_url_verify_escaped(_URLEncodedHost, + [OFCharacterSet URLHostAllowedCharacterSet]); if ((UTF8String = tmp) != NULL) { if ((tmp = strchr(UTF8String, '#')) != NULL) { *tmp = '\0'; @@ -698,22 +688,11 @@ bool isDirectory; @try { void *pool = objc_autoreleasePoolPush(); -#if defined(OF_WINDOWS) || defined(OF_MSDOS) - isDirectory = ([path hasSuffix: @"\\"] || - [path hasSuffix: @"/"] || - [OFFileURLHandler of_directoryExistsAtPath: path]); -#elif defined(OF_AMIGAOS) - isDirectory = ([path hasSuffix: @"/"] || - [path hasSuffix: @":"] || - [OFFileURLHandler of_directoryExistsAtPath: path]); -#else - isDirectory = ([path hasSuffix: @"/"] || - [OFFileURLHandler of_directoryExistsAtPath: path]); -#endif + isDirectory = [path of_isDirectoryPath]; objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; @@ -730,10 +709,11 @@ { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); + OFString *URLEncodedHost = nil; if (!path.absolutePath) { OFString *currentDirectoryPath = [OFFileManager defaultManager].currentDirectoryPath; @@ -740,27 +720,13 @@ path = [currentDirectoryPath stringByAppendingPathComponent: path]; path = path.stringByStandardizingPath; } -#ifdef OF_WINDOWS - if ([path hasPrefix: @"\\\\"]) { - OFArray *components = path.pathComponents; - - if (components.count < 2) - @throw [OFInvalidFormatException exception]; - - _URLEncodedHost = [[[components objectAtIndex: 1] - stringByURLEncodingWithAllowedCharacters: - [OFCharacterSet URLHostAllowedCharacterSet]] copy]; - path = [OFString pathWithComponents: - [components objectsInRange: - of_range(2, components.count - 2)]]; - } -#endif - - path = pathToURLPath(path); + path = [path + of_pathToURLPathWithURLEncodedHost: &URLEncodedHost]; + _URLEncodedHost = [URLEncodedHost copy]; if (isDirectory && ![path hasSuffix: @"/"]) path = [path stringByAppendingString: @"/"]; _URLEncodedScheme = @"file"; @@ -884,10 +850,21 @@ return _URLEncodedScheme; } - (OFString *)host { + if ([_URLEncodedHost hasPrefix: @"["] && + [_URLEncodedHost hasSuffix: @"]"]) { + OFString *host = [_URLEncodedHost substringWithRange: + of_range(1, _URLEncodedHost.length - 2)]; + + if (!of_url_is_ipv6_host(host)) + @throw [OFInvalidArgumentException exception]; + + return host; + } + return _URLEncodedHost.stringByURLDecoding; } - (OFString *)URLEncodedHost { @@ -930,22 +907,25 @@ } - (OFArray *)pathComponents { void *pool = objc_autoreleasePoolPush(); +#ifdef OF_HAVE_FILES + bool isFile = [_URLEncodedScheme isEqual: @"file"]; +#endif OFMutableArray *ret; size_t count; -#if defined(OF_WINDOWS) || defined(OF_MSDOS) - if ([_URLEncodedScheme isEqual: @"file"]) { - OFString *path = [_URLEncodedPath substringWithRange: - of_range(1, _URLEncodedPath.length - 1)]; - path = [path stringByReplacingOccurrencesOfString: @"/" - withString: @"\\"]; +#ifdef OF_HAVE_FILES + if (isFile) { + OFString *path = [_URLEncodedPath + of_URLPathToPathWithURLEncodedHost: nil]; ret = [[path.pathComponents mutableCopy] autorelease]; - [ret insertObject: @"/" - atIndex: 0]; + + if (![ret.firstObject isEqual: @"/"]) + [ret insertObject: @"/" + atIndex: 0]; } else #endif ret = [[[_URLEncodedPath componentsSeparatedByString: @"/"] mutableCopy] autorelease]; @@ -955,15 +935,17 @@ [ret replaceObjectAtIndex: 0 withObject: @"/"]; for (size_t i = 0; i < count; i++) { OFString *component = [ret objectAtIndex: i]; -#if defined(OF_WINDOWS) || defined(OF_MSDOS) - component = [component - stringByReplacingOccurrencesOfString: @"\\" - withString: @"/"]; + +#ifdef OF_HAVE_FILES + if (isFile) + component = + [component of_pathComponentToURLPathComponent]; #endif + [ret replaceObjectAtIndex: i withObject: component.stringByURLDecoding]; } [ret makeImmutable]; @@ -1106,32 +1088,11 @@ @throw [OFInvalidArgumentException exception]; if (![_URLEncodedPath hasPrefix: @"/"]) @throw [OFInvalidFormatException exception]; - path = self.path; - -#if !defined(OF_WINDOWS) && !defined(OF_MSDOS) - if (path.length > 1 && [path hasSuffix: @"/"]) -#else - if (path.length > 1 && [path hasSuffix: @"/"] && - ![path hasSuffix: @":/"]) -#endif - path = [path substringWithRange: of_range(0, path.length - 1)]; - - path = URLPathToPath(path); - -#ifdef OF_WINDOWS - if (_URLEncodedHost != nil) { - if (path.length == 0) - path = [OFString stringWithFormat: @"\\\\%@", - self.host]; - else - path = [OFString stringWithFormat: @"\\\\%@\\%@", - self.host, path]; - } -#endif + path = [self.path of_URLPathToPathWithURLEncodedHost: _URLEncodedHost]; [path retain]; objc_autoreleasePoolPop(pool); Index: src/OFZIPArchive.m ================================================================== --- src/OFZIPArchive.m +++ src/OFZIPArchive.m @@ -246,11 +246,12 @@ } #endif - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; [_stream release]; [_archiveComment release]; [_entries release]; [_pathToEntryMap release]; @@ -401,11 +402,15 @@ objc_autoreleasePoolPop(pool); } - (void)of_closeLastReturnedStream { - [_lastReturnedStream close]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } if ((_mode == OF_ZIP_ARCHIVE_MODE_WRITE || _mode == OF_ZIP_ARCHIVE_MODE_APPEND) && [_lastReturnedStream isKindOfClass: [OFZIPArchiveFileWriteStream class]]) { @@ -615,11 +620,11 @@ } - (void)close { if (_stream == nil) - return; + @throw [OFNotOpenException exceptionWithObject: self]; [self of_closeLastReturnedStream]; if (_mode == OF_ZIP_ARCHIVE_MODE_WRITE || _mode == OF_ZIP_ARCHIVE_MODE_APPEND) @@ -768,14 +773,13 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil || _decompressedStream != nil) + [self close]; - [_stream release]; - [_decompressedStream release]; [_entry release]; [super dealloc]; } @@ -845,10 +849,13 @@ .fileDescriptorForReading; } - (void)close { + if (_stream == nil || _decompressedStream == nil) + @throw [OFNotOpenException exceptionWithObject: self]; + [_stream release]; _stream = nil; [_decompressedStream release]; _decompressedStream = nil; @@ -870,13 +877,13 @@ return self; } - (void)dealloc { - [self close]; + if (_stream != nil) + [self close]; - [_stream release]; [_entry release]; [super dealloc]; } @@ -903,11 +910,11 @@ } - (void)close { if (_stream == nil) - return; + @throw [OFNotOpenException exceptionWithObject: self]; [_stream writeLittleEndianInt32: 0x08074B50]; [_stream writeLittleEndianInt32: _CRC32]; [_stream writeLittleEndianInt64: _bytesWritten]; [_stream writeLittleEndianInt64: _bytesWritten]; @@ -919,7 +926,9 @@ _entry.compressedSize = _bytesWritten; _entry.uncompressedSize = _bytesWritten; [_entry makeImmutable]; _bytesWritten += (2 * 4 + 2 * 8); + + [super close]; } @end Index: src/condition.m ================================================================== --- src/condition.m +++ src/condition.m @@ -15,14 +15,14 @@ * file. */ #include "config.h" -#import "condition.h" +#include "platform.h" #if defined(OF_HAVE_PTHREADS) -# include "condition_pthread.m" +# include "platform/posix/condition.m" #elif defined(OF_WINDOWS) -# include "condition_winapi.m" +# include "platform/windows/condition.m" #elif defined(OF_AMIGAOS) -# include "condition_amiga.m" +# include "platform/amiga/condition.m" #endif DELETED src/condition_amiga.m Index: src/condition_amiga.m ================================================================== --- src/condition_amiga.m +++ src/condition_amiga.m @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include - -#include -#include -#ifndef OF_AMIGAOS4 -# include -#endif - -bool -of_condition_new(of_condition_t *condition) -{ - condition->waitingTasks = NULL; - - return true; -} - -bool -of_condition_signal(of_condition_t *condition) -{ - Forbid(); - @try { - if (condition->waitingTasks == NULL) - return true; - - Signal(condition->waitingTasks->task, - (1ul << condition->waitingTasks->sigBit)); - - condition->waitingTasks = condition->waitingTasks->next; - } @finally { - Permit(); - } - - return true; -} - -bool -of_condition_broadcast(of_condition_t *condition) -{ - Forbid(); - @try { - if (condition->waitingTasks == NULL) - return true; - - while (condition->waitingTasks != NULL) { - Signal(condition->waitingTasks->task, - (1ul << condition->waitingTasks->sigBit)); - - condition->waitingTasks = condition->waitingTasks->next; - } - } @finally { - Permit(); - } - - return true; -} - -bool -of_condition_wait(of_condition_t *condition, of_mutex_t *mutex) -{ - ULONG signalMask = 0; - - return of_condition_wait_or_signal(condition, mutex, &signalMask); -} - -bool -of_condition_wait_or_signal(of_condition_t *condition, of_mutex_t *mutex, - ULONG *signalMask) -{ - struct of_condition_waiting_task waitingTask = { - .task = FindTask(NULL), - .sigBit = AllocSignal(-1) - }; - bool ret; - ULONG mask; - - if (waitingTask.sigBit == -1) { - errno = EAGAIN; - return false; - } - - Forbid(); - - if (!of_mutex_unlock(mutex)) { - FreeSignal(waitingTask.sigBit); - return false; - } - - waitingTask.next = condition->waitingTasks; - condition->waitingTasks = &waitingTask; - - mask = Wait((1ul << waitingTask.sigBit) | *signalMask); - if (mask & (1ul << waitingTask.sigBit) || (*signalMask &= mask)) - ret = of_mutex_lock(mutex); - else { - /* - * This should not happen - it means something interrupted the - * Wait(), so the best we can do is return EINTR. - */ - ret = false; - errno = EINTR; - } - - FreeSignal(waitingTask.sigBit); - - Permit(); - - return ret; -} - -bool -of_condition_timed_wait(of_condition_t *condition, of_mutex_t *mutex, - of_time_interval_t timeout) -{ - ULONG signalMask = 0; - - return of_condition_timed_wait_or_signal(condition, mutex, timeout, - &signalMask); -} - -bool -of_condition_timed_wait_or_signal(of_condition_t *condition, of_mutex_t *mutex, - of_time_interval_t timeout, ULONG *signalMask) -{ - struct of_condition_waiting_task waitingTask = { - .task = FindTask(NULL), - .sigBit = AllocSignal(-1) - }; - struct MsgPort port = { - .mp_Node = { - .ln_Type = NT_MSGPORT - }, - .mp_Flags = PA_SIGNAL, - .mp_SigTask = waitingTask.task, - .mp_SigBit = AllocSignal(-1) - }; -#ifdef OF_AMIGAOS4 - struct TimeRequest request = { - .Request = { -#else - struct timerequest request = { - .tr_node = { -#endif - .io_Message = { - .mn_Node = { - .ln_Type = NT_MESSAGE - }, - .mn_ReplyPort = &port, - .mn_Length = sizeof(request) - }, - .io_Command = TR_ADDREQUEST - }, -#ifdef OF_AMIGAOS4 - .Time = { - .Seconds = (ULONG)timeout, - .Microseconds = - (timeout - request.Time.Seconds) * 1000000 -#else - .tr_time = { - .tv_sec = (ULONG)timeout, - .tv_micro = (timeout - request.tr_time.tv_sec) * 1000000 -#endif - } - }; - ULONG mask; - bool ret; - - NewList(&port.mp_MsgList); - - if (waitingTask.sigBit == -1 || port.mp_SigBit == -1) { - errno = EAGAIN; - goto fail; - } - - if (OpenDevice("timer.device", UNIT_MICROHZ, - (struct IORequest *)&request, 0) != 0) { - errno = EAGAIN; - goto fail; - } - - Forbid(); - - if (!of_mutex_unlock(mutex)) { - Permit(); - goto fail; - } - - waitingTask.next = condition->waitingTasks; - condition->waitingTasks = &waitingTask; - - SendIO((struct IORequest *)&request); - - mask = Wait((1ul << waitingTask.sigBit) | (1ul << port.mp_SigBit) | - *signalMask); - if (mask & (1ul << waitingTask.sigBit) || (*signalMask &= mask)) - ret = of_mutex_lock(mutex); - else if (mask & (1ul << port.mp_SigBit)) { - ret = false; - errno = ETIMEDOUT; - } else { - /* - * This should not happen - it means something interrupted the - * Wait(), so the best we can do is return EINTR. - */ - ret = false; - errno = EINTR; - } - - condition->waitingTasks = waitingTask.next; - - if (!CheckIO((struct IORequest *)&request)) { - AbortIO((struct IORequest *)&request); - WaitIO((struct IORequest *)&request); - } - CloseDevice((struct IORequest *)&request); - - Permit(); - - FreeSignal(waitingTask.sigBit); - FreeSignal(port.mp_SigBit); - - return ret; - -fail: - if (waitingTask.sigBit != -1) - FreeSignal(waitingTask.sigBit); - if (port.mp_SigBit != -1) - FreeSignal(port.mp_SigBit); - - return false; -} - -bool -of_condition_free(of_condition_t *condition) -{ - Forbid(); - @try { - if (condition->waitingTasks != NULL) { - errno = EBUSY; - return false; - } - } @finally { - Permit(); - } - - return true; -} DELETED src/condition_pthread.m Index: src/condition_pthread.m ================================================================== --- src/condition_pthread.m +++ src/condition_pthread.m @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -bool -of_condition_new(of_condition_t *condition) -{ - return (pthread_cond_init(condition, NULL) == 0); -} - -bool -of_condition_signal(of_condition_t *condition) -{ - return (pthread_cond_signal(condition) == 0); -} - -bool -of_condition_broadcast(of_condition_t *condition) -{ - return (pthread_cond_broadcast(condition) == 0); -} - -bool -of_condition_wait(of_condition_t *condition, of_mutex_t *mutex) -{ - return (pthread_cond_wait(condition, mutex) == 0); -} - -bool -of_condition_timed_wait(of_condition_t *condition, of_mutex_t *mutex, - of_time_interval_t timeout) -{ - struct timespec ts; - - ts.tv_sec = (time_t)timeout; - ts.tv_nsec = (timeout - ts.tv_sec) * 1000000000; - - return (pthread_cond_timedwait(condition, mutex, &ts) == 0); -} - -bool -of_condition_free(of_condition_t *condition) -{ - return (pthread_cond_destroy(condition) == 0); -} DELETED src/condition_winapi.m Index: src/condition_winapi.m ================================================================== --- src/condition_winapi.m +++ src/condition_winapi.m @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include - -bool -of_condition_new(of_condition_t *condition) -{ - condition->count = 0; - - if ((condition->event = CreateEvent(NULL, FALSE, 0, NULL)) == NULL) { - errno = EAGAIN; - return false; - } - - return true; -} - -bool -of_condition_signal(of_condition_t *condition) -{ - if (!SetEvent(condition->event)) { - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - errno = EINVAL; - return false; - default: - OF_ENSURE(0); - } - } - - return true; -} - -bool -of_condition_broadcast(of_condition_t *condition) -{ - int count = condition->count; - - for (int i = 0; i < count; i++) { - if (!SetEvent(condition->event)) { - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - errno = EINVAL; - return false; - default: - OF_ENSURE(0); - } - } - } - - return true; -} - -bool -of_condition_wait(of_condition_t *condition, of_mutex_t *mutex) -{ - DWORD status; - - if (!of_mutex_unlock(mutex)) - return false; - - of_atomic_int_inc(&condition->count); - status = WaitForSingleObject(condition->event, INFINITE); - of_atomic_int_dec(&condition->count); - - switch (status) { - case WAIT_OBJECT_0: - return of_mutex_lock(mutex); - case WAIT_FAILED: - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - errno = EINVAL; - return false; - default: - OF_ENSURE(0); - } - default: - OF_ENSURE(0); - } -} - -bool -of_condition_timed_wait(of_condition_t *condition, of_mutex_t *mutex, - of_time_interval_t timeout) -{ - DWORD status; - - if (!of_mutex_unlock(mutex)) - return false; - - of_atomic_int_inc(&condition->count); - status = WaitForSingleObject(condition->event, timeout * 1000); - of_atomic_int_dec(&condition->count); - - switch (status) { - case WAIT_OBJECT_0: - return of_mutex_lock(mutex); - case WAIT_TIMEOUT: - errno = ETIMEDOUT; - return false; - case WAIT_FAILED: - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - errno = EINVAL; - return false; - default: - OF_ENSURE(0); - } - default: - OF_ENSURE(0); - } -} - -bool -of_condition_free(of_condition_t *condition) -{ - if (condition->count != 0) { - errno = EBUSY; - return false; - } - - return CloseHandle(condition->event); -} Index: src/mutex.m ================================================================== --- src/mutex.m +++ src/mutex.m @@ -15,108 +15,14 @@ * file. */ #include "config.h" -#import "mutex.h" +#include "platform.h" #if defined(OF_HAVE_PTHREADS) -# include "mutex_pthread.m" +# include "platform/posix/mutex.m" #elif defined(OF_WINDOWS) -# include "mutex_winapi.m" +# include "platform/windows/mutex.m" #elif defined(OF_AMIGAOS) -# include "mutex_amiga.m" -#endif - -#if !defined(OF_HAVE_RECURSIVE_PTHREAD_MUTEXES) && !defined(OF_WINDOWS) && \ - !defined(OF_AMIGAOS) -bool -of_rmutex_new(of_rmutex_t *rmutex) -{ - if (!of_mutex_new(&rmutex->mutex)) - return false; - - if (!of_tlskey_new(&rmutex->count)) - return false; - - return true; -} - -bool -of_rmutex_lock(of_rmutex_t *rmutex) -{ - uintptr_t count = (uintptr_t)of_tlskey_get(rmutex->count); - - if (count > 0) { - if (!of_tlskey_set(rmutex->count, (void *)(count + 1))) - return false; - - return true; - } - - if (!of_mutex_lock(&rmutex->mutex)) - return false; - - if (!of_tlskey_set(rmutex->count, (void *)1)) { - of_mutex_unlock(&rmutex->mutex); - return false; - } - - return true; -} - -bool -of_rmutex_trylock(of_rmutex_t *rmutex) -{ - uintptr_t count = (uintptr_t)of_tlskey_get(rmutex->count); - - if (count > 0) { - if (!of_tlskey_set(rmutex->count, (void *)(count + 1))) - return false; - - return true; - } - - if (!of_mutex_trylock(&rmutex->mutex)) - return false; - - if (!of_tlskey_set(rmutex->count, (void *)1)) { - of_mutex_unlock(&rmutex->mutex); - return false; - } - - return true; -} - -bool -of_rmutex_unlock(of_rmutex_t *rmutex) -{ - uintptr_t count = (uintptr_t)of_tlskey_get(rmutex->count); - - if (count > 1) { - if (!of_tlskey_set(rmutex->count, (void *)(count - 1))) - return false; - - return true; - } - - if (!of_tlskey_set(rmutex->count, (void *)0)) - return false; - - if (!of_mutex_unlock(&rmutex->mutex)) - return false; - - return true; -} - -bool -of_rmutex_free(of_rmutex_t *rmutex) -{ - if (!of_mutex_free(&rmutex->mutex)) - return false; - - if (!of_tlskey_free(rmutex->count)) - return false; - - return true; -} +# include "platform/amiga/mutex.m" #endif DELETED src/mutex_amiga.m Index: src/mutex_amiga.m ================================================================== --- src/mutex_amiga.m +++ src/mutex_amiga.m @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include - -#include - -bool -of_mutex_new(of_mutex_t *mutex) -{ - InitSemaphore(mutex); - - return true; -} - -bool -of_mutex_lock(of_mutex_t *mutex) -{ - ObtainSemaphore(mutex); - - return true; -} - -bool -of_mutex_trylock(of_mutex_t *mutex) -{ - if (!AttemptSemaphore(mutex)) { - errno = EBUSY; - return false; - } - - return true; -} - -bool -of_mutex_unlock(of_mutex_t *mutex) -{ - ReleaseSemaphore(mutex); - - return true; -} - -bool -of_mutex_free(of_mutex_t *mutex) -{ - return true; -} - -bool -of_rmutex_new(of_rmutex_t *rmutex) -{ - return of_mutex_new(rmutex); -} - -bool -of_rmutex_lock(of_rmutex_t *rmutex) -{ - return of_mutex_lock(rmutex); -} - -bool -of_rmutex_trylock(of_rmutex_t *rmutex) -{ - return of_mutex_trylock(rmutex); -} - -bool -of_rmutex_unlock(of_rmutex_t *rmutex) -{ - return of_mutex_unlock(rmutex); -} - -bool -of_rmutex_free(of_rmutex_t *rmutex) -{ - return of_mutex_free(rmutex); -} DELETED src/mutex_pthread.m Index: src/mutex_pthread.m ================================================================== --- src/mutex_pthread.m +++ src/mutex_pthread.m @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -bool -of_mutex_new(of_mutex_t *mutex) -{ - return (pthread_mutex_init(mutex, NULL) == 0); -} - -bool -of_mutex_lock(of_mutex_t *mutex) -{ - return (pthread_mutex_lock(mutex) == 0); -} - -bool -of_mutex_trylock(of_mutex_t *mutex) -{ - return (pthread_mutex_trylock(mutex) == 0); -} - -bool -of_mutex_unlock(of_mutex_t *mutex) -{ - return (pthread_mutex_unlock(mutex) == 0); -} - -bool -of_mutex_free(of_mutex_t *mutex) -{ - return (pthread_mutex_destroy(mutex) == 0); -} - -#ifdef OF_HAVE_RECURSIVE_PTHREAD_MUTEXES -bool -of_rmutex_new(of_rmutex_t *rmutex) -{ - pthread_mutexattr_t attr; - - if (pthread_mutexattr_init(&attr) != 0) - return false; - - if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) - return false; - - if (pthread_mutex_init(rmutex, &attr) != 0) - return false; - - if (pthread_mutexattr_destroy(&attr) != 0) - return false; - - return true; -} - -bool -of_rmutex_lock(of_rmutex_t *rmutex) -{ - return of_mutex_lock(rmutex); -} - -bool -of_rmutex_trylock(of_rmutex_t *rmutex) -{ - return of_mutex_trylock(rmutex); -} - -bool -of_rmutex_unlock(of_rmutex_t *rmutex) -{ - return of_mutex_unlock(rmutex); -} - -bool -of_rmutex_free(of_rmutex_t *rmutex) -{ - return of_mutex_free(rmutex); -} -#endif DELETED src/mutex_winapi.m Index: src/mutex_winapi.m ================================================================== --- src/mutex_winapi.m +++ src/mutex_winapi.m @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include - -bool -of_mutex_new(of_mutex_t *mutex) -{ - InitializeCriticalSection(mutex); - - return true; -} - -bool -of_mutex_lock(of_mutex_t *mutex) -{ - EnterCriticalSection(mutex); - - return true; -} - -bool -of_mutex_trylock(of_mutex_t *mutex) -{ - if (!TryEnterCriticalSection(mutex)) { - errno = EBUSY; - return false; - } - - return true; -} - -bool -of_mutex_unlock(of_mutex_t *mutex) -{ - LeaveCriticalSection(mutex); - - return true; -} - -bool -of_mutex_free(of_mutex_t *mutex) -{ - DeleteCriticalSection(mutex); - - return true; -} - -bool -of_rmutex_new(of_rmutex_t *rmutex) -{ - return of_mutex_new(rmutex); -} - -bool -of_rmutex_lock(of_rmutex_t *rmutex) -{ - return of_mutex_lock(rmutex); -} - -bool -of_rmutex_trylock(of_rmutex_t *rmutex) -{ - return of_mutex_trylock(rmutex); -} - -bool -of_rmutex_unlock(of_rmutex_t *rmutex) -{ - return of_mutex_unlock(rmutex); -} - -bool -of_rmutex_free(of_rmutex_t *rmutex) -{ - return of_mutex_free(rmutex); -} ADDED src/platform/amiga/OFString+PathAdditions.m Index: src/platform/amiga/OFString+PathAdditions.m ================================================================== --- src/platform/amiga/OFString+PathAdditions.m +++ src/platform/amiga/OFString+PathAdditions.m @@ -0,0 +1,341 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFString+PathAdditions.h" +#import "OFArray.h" +#import "OFFileURLHandler.h" + +#import "OFOutOfRangeException.h" + +int _OFString_PathAdditions_reference; + +@implementation OFString (PathAdditions) ++ (OFString *)pathWithComponents: (OFArray *)components +{ + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + bool firstAfterDevice = true; + + for (OFString *component in components) { + if (component.length == 0) + continue; + + if (!firstAfterDevice) + [ret appendString: @"/"]; + + [ret appendString: component]; + + if (![component hasSuffix: @":"]) + firstAfterDevice = false; + } + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)isAbsolutePath +{ + return [self containsString: @":"]; +} + +- (OFArray *)pathComponents +{ + OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + const char *cString = self.UTF8String; + size_t i, last = 0, cStringLength = self.UTF8StringLength; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return ret; + } + + for (i = 0; i < cStringLength; i++) { + if (cString[i] == '/') { + if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + else + [ret addObject: @"/"]; + + last = i + 1; + } else if (cString[i] == ':') { + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last + 1]]; + + last = i + 1; + } + } + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (OFString *)lastPathComponent +{ + /* + * AmigaOS needs the full parsing to determine the last path component. + * This could be optimized by not creating the temporary objects, + * though. + */ + void *pool = objc_autoreleasePoolPush(); + OFString *ret = self.pathComponents.lastObject; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)pathExtension +{ + void *pool = objc_autoreleasePoolPush(); + OFString *ret, *fileName; + size_t pos; + + fileName = self.lastPathComponent; + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + ret = [fileName substringWithRange: + of_range(pos + 1, fileName.length - pos - 1)]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingLastPathComponent +{ + /* + * AmigaOS needs the full parsing to delete the last path component. + * This could be optimized, though. + */ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components = self.pathComponents; + size_t count = components.count; + OFString *ret; + + if (count < 2) { + if ([components.firstObject hasSuffix: @":"]) { + ret = [components.firstObject retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; + } + + objc_autoreleasePoolPop(pool); + return @""; + } + + components = [components objectsInRange: + of_range(0, components.count - 1)]; + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingPathExtension +{ + void *pool; + OFMutableArray OF_GENERIC(OFString *) *components; + OFString *ret, *fileName; + size_t pos; + + if (self.length == 0) + return [[self copy] autorelease]; + + pool = objc_autoreleasePoolPush(); + components = [[self.pathComponents mutableCopy] autorelease]; + fileName = components.lastObject; + + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + fileName = [fileName substringWithRange: of_range(0, pos)]; + [components replaceObjectAtIndex: components.count - 1 + withObject: fileName]; + + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByStandardizingPath +{ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components; + OFMutableArray OF_GENERIC(OFString *) *array; + OFString *ret; + bool done = false; + + if (self.length == 0) + return @""; + + components = self.pathComponents; + + if (components.count == 1) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + array = [[components mutableCopy] autorelease]; + + while (!done) { + size_t length = array.count; + + done = true; + + for (size_t i = 0; i < length; i++) { + OFString *component = [array objectAtIndex: i]; + OFString *parent = + (i > 0 ? [array objectAtIndex: i - 1] : 0); + + if (component.length == 0) { + [array removeObjectAtIndex: i]; + + done = false; + break; + } + + if ([component isEqual: @"/"] && + parent != nil && ![parent isEqual: @"/"]) { + [array removeObjectsInRange: + of_range(i - 1, 2)]; + + done = false; + break; + } + } + } + + ret = [OFString pathWithComponents: array]; + + if ([self hasSuffix: @"/"]) + ret = [ret stringByAppendingString: @"/"]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByAppendingPathComponent: (OFString *)component +{ + if ([self hasSuffix: @"/"] || [self hasSuffix: @":"]) + return [self stringByAppendingString: component]; + else { + OFMutableString *ret = [[self mutableCopy] autorelease]; + + [ret appendString: @"/"]; + [ret appendString: component]; + + [ret makeImmutable]; + + return ret; + } +} + +- (bool)of_isDirectoryPath +{ + return ([self hasSuffix: @"/"] || [self hasSuffix: @":"] || + [OFFileURLHandler of_directoryExistsAtPath: self]); +} + +- (OFString *)of_pathToURLPathWithURLEncodedHost: (OFString **)URLEncodedHost +{ + OFArray OF_GENERIC(OFString *) *components = self.pathComponents; + OFMutableString *ret = [OFMutableString string]; + + for (OFString *component in components) { + if (component.length == 0) + continue; + + if ([component isEqual: @"/"]) + [ret appendString: @"/.."]; + else { + [ret appendString: @"/"]; + [ret appendString: component]; + } + } + + [ret makeImmutable]; + + return ret; +} + +- (OFString *)of_URLPathToPathWithURLEncodedHost: (OFString *)URLEncodedHost +{ + OFString *path = self; + + if (path.length > 1 && [path hasSuffix: @"/"]) + path = [path substringWithRange: of_range(0, path.length - 1)]; + + OFMutableArray OF_GENERIC(OFString *) *components; + size_t count; + + path = [path substringWithRange: of_range(1, path.length - 1)]; + components = [[[path + componentsSeparatedByString: @"/"] mutableCopy] autorelease]; + count = components.count; + + for (size_t i = 0; i < count; i++) { + OFString *component = [components objectAtIndex: i]; + + if ([component isEqual: @"."]) { + [components removeObjectAtIndex: i]; + count--; + + i--; + continue; + } + + if ([component isEqual: @".."]) + [components replaceObjectAtIndex: i + withObject: @"/"]; + } + + return [OFString pathWithComponents: components]; +} + +- (OFString *)of_pathComponentToURLPathComponent +{ + return self; +} +@end ADDED src/platform/amiga/condition.m Index: src/platform/amiga/condition.m ================================================================== --- src/platform/amiga/condition.m +++ src/platform/amiga/condition.m @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "condition.h" + +#include +#include +#ifndef OF_AMIGAOS4 +# include +#endif + +bool +of_condition_new(of_condition_t *condition) +{ + condition->waitingTasks = NULL; + + return true; +} + +bool +of_condition_signal(of_condition_t *condition) +{ + Forbid(); + @try { + if (condition->waitingTasks == NULL) + return true; + + Signal(condition->waitingTasks->task, + (1ul << condition->waitingTasks->sigBit)); + + condition->waitingTasks = condition->waitingTasks->next; + } @finally { + Permit(); + } + + return true; +} + +bool +of_condition_broadcast(of_condition_t *condition) +{ + Forbid(); + @try { + if (condition->waitingTasks == NULL) + return true; + + while (condition->waitingTasks != NULL) { + Signal(condition->waitingTasks->task, + (1ul << condition->waitingTasks->sigBit)); + + condition->waitingTasks = condition->waitingTasks->next; + } + } @finally { + Permit(); + } + + return true; +} + +bool +of_condition_wait(of_condition_t *condition, of_mutex_t *mutex) +{ + ULONG signalMask = 0; + + return of_condition_wait_or_signal(condition, mutex, &signalMask); +} + +bool +of_condition_wait_or_signal(of_condition_t *condition, of_mutex_t *mutex, + ULONG *signalMask) +{ + struct of_condition_waiting_task waitingTask = { + .task = FindTask(NULL), + .sigBit = AllocSignal(-1) + }; + bool ret; + ULONG mask; + + if (waitingTask.sigBit == -1) { + errno = EAGAIN; + return false; + } + + Forbid(); + + if (!of_mutex_unlock(mutex)) { + FreeSignal(waitingTask.sigBit); + return false; + } + + waitingTask.next = condition->waitingTasks; + condition->waitingTasks = &waitingTask; + + mask = Wait((1ul << waitingTask.sigBit) | *signalMask); + if (mask & (1ul << waitingTask.sigBit) || (*signalMask &= mask)) + ret = of_mutex_lock(mutex); + else { + /* + * This should not happen - it means something interrupted the + * Wait(), so the best we can do is return EINTR. + */ + ret = false; + errno = EINTR; + } + + FreeSignal(waitingTask.sigBit); + + Permit(); + + return ret; +} + +bool +of_condition_timed_wait(of_condition_t *condition, of_mutex_t *mutex, + of_time_interval_t timeout) +{ + ULONG signalMask = 0; + + return of_condition_timed_wait_or_signal(condition, mutex, timeout, + &signalMask); +} + +bool +of_condition_timed_wait_or_signal(of_condition_t *condition, of_mutex_t *mutex, + of_time_interval_t timeout, ULONG *signalMask) +{ + struct of_condition_waiting_task waitingTask = { + .task = FindTask(NULL), + .sigBit = AllocSignal(-1) + }; + struct MsgPort port = { + .mp_Node = { + .ln_Type = NT_MSGPORT + }, + .mp_Flags = PA_SIGNAL, + .mp_SigTask = waitingTask.task, + .mp_SigBit = AllocSignal(-1) + }; +#ifdef OF_AMIGAOS4 + struct TimeRequest request = { + .Request = { +#else + struct timerequest request = { + .tr_node = { +#endif + .io_Message = { + .mn_Node = { + .ln_Type = NT_MESSAGE + }, + .mn_ReplyPort = &port, + .mn_Length = sizeof(request) + }, + .io_Command = TR_ADDREQUEST + }, +#ifdef OF_AMIGAOS4 + .Time = { + .Seconds = (ULONG)timeout, + .Microseconds = + (timeout - request.Time.Seconds) * 1000000 +#else + .tr_time = { + .tv_sec = (ULONG)timeout, + .tv_micro = (timeout - request.tr_time.tv_sec) * 1000000 +#endif + } + }; + ULONG mask; + bool ret; + + NewList(&port.mp_MsgList); + + if (waitingTask.sigBit == -1 || port.mp_SigBit == -1) { + errno = EAGAIN; + goto fail; + } + + if (OpenDevice("timer.device", UNIT_MICROHZ, + (struct IORequest *)&request, 0) != 0) { + errno = EAGAIN; + goto fail; + } + + Forbid(); + + if (!of_mutex_unlock(mutex)) { + Permit(); + goto fail; + } + + waitingTask.next = condition->waitingTasks; + condition->waitingTasks = &waitingTask; + + SendIO((struct IORequest *)&request); + + mask = Wait((1ul << waitingTask.sigBit) | (1ul << port.mp_SigBit) | + *signalMask); + if (mask & (1ul << waitingTask.sigBit) || (*signalMask &= mask)) + ret = of_mutex_lock(mutex); + else if (mask & (1ul << port.mp_SigBit)) { + ret = false; + errno = ETIMEDOUT; + } else { + /* + * This should not happen - it means something interrupted the + * Wait(), so the best we can do is return EINTR. + */ + ret = false; + errno = EINTR; + } + + condition->waitingTasks = waitingTask.next; + + if (!CheckIO((struct IORequest *)&request)) { + AbortIO((struct IORequest *)&request); + WaitIO((struct IORequest *)&request); + } + CloseDevice((struct IORequest *)&request); + + Permit(); + + FreeSignal(waitingTask.sigBit); + FreeSignal(port.mp_SigBit); + + return ret; + +fail: + if (waitingTask.sigBit != -1) + FreeSignal(waitingTask.sigBit); + if (port.mp_SigBit != -1) + FreeSignal(port.mp_SigBit); + + return false; +} + +bool +of_condition_free(of_condition_t *condition) +{ + Forbid(); + @try { + if (condition->waitingTasks != NULL) { + errno = EBUSY; + return false; + } + } @finally { + Permit(); + } + + return true; +} ADDED src/platform/amiga/mutex.m Index: src/platform/amiga/mutex.m ================================================================== --- src/platform/amiga/mutex.m +++ src/platform/amiga/mutex.m @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "mutex.h" + +#include + +bool +of_mutex_new(of_mutex_t *mutex) +{ + InitSemaphore(mutex); + + return true; +} + +bool +of_mutex_lock(of_mutex_t *mutex) +{ + ObtainSemaphore(mutex); + + return true; +} + +bool +of_mutex_trylock(of_mutex_t *mutex) +{ + if (!AttemptSemaphore(mutex)) { + errno = EBUSY; + return false; + } + + return true; +} + +bool +of_mutex_unlock(of_mutex_t *mutex) +{ + ReleaseSemaphore(mutex); + + return true; +} + +bool +of_mutex_free(of_mutex_t *mutex) +{ + return true; +} + +bool +of_rmutex_new(of_rmutex_t *rmutex) +{ + return of_mutex_new(rmutex); +} + +bool +of_rmutex_lock(of_rmutex_t *rmutex) +{ + return of_mutex_lock(rmutex); +} + +bool +of_rmutex_trylock(of_rmutex_t *rmutex) +{ + return of_mutex_trylock(rmutex); +} + +bool +of_rmutex_unlock(of_rmutex_t *rmutex) +{ + return of_mutex_unlock(rmutex); +} + +bool +of_rmutex_free(of_rmutex_t *rmutex) +{ + return of_mutex_free(rmutex); +} ADDED src/platform/amiga/thread.m Index: src/platform/amiga/thread.m ================================================================== --- src/platform/amiga/thread.m +++ src/platform/amiga/thread.m @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#import "OFData.h" + +#import "thread.h" +#import "tlskey.h" + +#include +#include +#include + +extern void of_tlskey_thread_exited(void); +static of_tlskey_t threadKey; + +OF_CONSTRUCTOR() +{ + OF_ENSURE(of_tlskey_new(&threadKey)); +} + +static void +functionWrapper(void) +{ + bool detached = false; + of_thread_t thread = + (of_thread_t)((struct Process *)FindTask(NULL))->pr_ExitData; + OF_ENSURE(of_tlskey_set(threadKey, thread)); + + thread->function(thread->object); + + ObtainSemaphore(&thread->semaphore); + @try { + thread->done = true; + + of_tlskey_thread_exited(); + + if (thread->detached) + detached = true; + else if (thread->joinTask != NULL) + Signal(thread->joinTask, (1ul << thread->joinSigBit)); + } @finally { + ReleaseSemaphore(&thread->semaphore); + } + + if (detached) + free(thread); +} + +bool +of_thread_attr_init(of_thread_attr_t *attr) +{ + attr->priority = 0; + attr->stackSize = 0; + + return true; +} + +bool +of_thread_new(of_thread_t *thread, const char *name, void (*function)(id), + id object, const of_thread_attr_t *attr) +{ + OFMutableData *tags = nil; + + if ((*thread = calloc(1, sizeof(**thread))) == NULL) { + errno = ENOMEM; + return false; + } + + @try { + (*thread)->function = function; + (*thread)->object = object; + InitSemaphore(&(*thread)->semaphore); + + tags = [[OFMutableData alloc] + initWithItemSize: sizeof(struct TagItem) + capacity: 12]; +#define ADD_TAG(tag, data) \ + { \ + struct TagItem t = { \ + .ti_Tag = tag, \ + .ti_Data = data \ + }; \ + [tags addItem: &t]; \ + } + ADD_TAG(NP_Entry, (ULONG)functionWrapper) + ADD_TAG(NP_ExitData, (ULONG)*thread) +#ifdef OF_AMIGAOS4 + ADD_TAG(NP_Child, TRUE) +#endif +#ifdef OF_MORPHOS + ADD_TAG(NP_CodeType, CODETYPE_PPC); +#endif + if (name != NULL) + ADD_TAG(NP_Name, (ULONG)name); + + ADD_TAG(NP_Input, ((struct Process *)FindTask(NULL))->pr_CIS) + ADD_TAG(NP_Output, ((struct Process *)FindTask(NULL))->pr_COS) + ADD_TAG(NP_Error, ((struct Process *)FindTask(NULL))->pr_CES) + ADD_TAG(NP_CloseInput, FALSE) + ADD_TAG(NP_CloseOutput, FALSE) + ADD_TAG(NP_CloseError, FALSE) + + if (attr != NULL && attr->priority != 0) { + if (attr->priority < 1 || attr->priority > 1) { + errno = EINVAL; + return false; + } + + /* + * -1 should be -128 (lowest possible priority) while + * +1 should be +127 (highest possible priority). + */ + ADD_TAG(NP_Priority, (attr->priority > 0 + ? attr->priority * 127 : attr->priority * 128)) + } + + if (attr != NULL && attr->stackSize != 0) + ADD_TAG(NP_StackSize, attr->stackSize) + else + ADD_TAG(NP_StackSize, + ((struct Process *)FindTask(NULL))->pr_StackSize) + + ADD_TAG(TAG_DONE, 0) +#undef ADD_TAG + + (*thread)->task = (struct Task *)CreateNewProc(tags.items); + if ((*thread)->task == NULL) { + free(*thread); + errno = EAGAIN; + return false; + } + } @catch (id e) { + free(*thread); + @throw e; + } @finally { + [tags release]; + } + + return true; +} + +of_thread_t +of_thread_current(void) +{ + return of_tlskey_get(threadKey); +} + +bool +of_thread_join(of_thread_t thread) +{ + ObtainSemaphore(&thread->semaphore); + @try { + if (thread->done) { + free(thread); + return true; + } + + if (thread->detached || thread->joinTask != NULL) { + errno = EINVAL; + return false; + } + + if ((thread->joinSigBit = AllocSignal(-1)) == -1) { + errno = EAGAIN; + return false; + } + + thread->joinTask = FindTask(NULL); + } @finally { + ReleaseSemaphore(&thread->semaphore); + } + + Wait(1ul << thread->joinSigBit); + FreeSignal(thread->joinSigBit); + + assert(thread->done); + free(thread); + + return true; +} + +bool +of_thread_detach(of_thread_t thread) +{ + ObtainSemaphore(&thread->semaphore); + + if (thread->done) + free(thread); + else + thread->detached = true; + + ReleaseSemaphore(&thread->semaphore); + + return true; +} + +void +of_thread_set_name(const char *name) +{ +} ADDED src/platform/amiga/tlskey.m Index: src/platform/amiga/tlskey.m ================================================================== --- src/platform/amiga/tlskey.m +++ src/platform/amiga/tlskey.m @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "tlskey.h" + +#include +#include + +/* + * As we use this file in both the runtime and ObjFW, and since AmigaOS always + * has the runtime, use the hashtable from the runtime. + */ +#import "runtime/private.h" + +static of_tlskey_t firstKey = NULL, lastKey = NULL; +static struct SignalSemaphore semaphore; +static bool semaphoreInitialized = false; + +static uint32_t +hashFunc(const void *ptr) +{ + return (uint32_t)(uintptr_t)ptr; +} + +static bool +equalFunc(const void *ptr1, const void *ptr2) +{ + return (ptr1 == ptr2); +} + +OF_CONSTRUCTOR() +{ + if (!semaphoreInitialized) { + InitSemaphore(&semaphore); + semaphoreInitialized = true; + } +} + +bool +of_tlskey_new(of_tlskey_t *key) +{ + if (!semaphoreInitialized) { + /* + * We might be called from another constructor, while ours has + * not run yet. This is safe, as the constructor is definitely + * run before a thread is spawned. + */ + InitSemaphore(&semaphore); + semaphoreInitialized = true; + } + + if ((*key = malloc(sizeof(**key))) == NULL) + return false; + + (*key)->table = NULL; + + ObtainSemaphore(&semaphore); + @try { + (*key)->next = NULL; + (*key)->previous = lastKey; + + if (lastKey != NULL) + lastKey->next = *key; + + lastKey = *key; + + if (firstKey == NULL) + firstKey = *key; + } @finally { + ReleaseSemaphore(&semaphore); + } + + /* We create the hash table lazily. */ + return true; +} + +bool +of_tlskey_free(of_tlskey_t key) +{ + ObtainSemaphore(&semaphore); + @try { + if (key->previous != NULL) + key->previous->next = key->next; + if (key->next != NULL) + key->next->previous = key->previous; + + if (firstKey == key) + firstKey = key->next; + if (lastKey == key) + lastKey = key->previous; + + objc_hashtable_free(key->table); + free(key); + } @finally { + ReleaseSemaphore(&semaphore); + } + + return true; +} + +void * +of_tlskey_get(of_tlskey_t key) +{ + void *ret; + + ObtainSemaphore(&semaphore); + @try { + if (key->table == NULL) + return NULL; + + ret = objc_hashtable_get(key->table, FindTask(NULL)); + } @finally { + ReleaseSemaphore(&semaphore); + } + + return ret; +} + +bool +of_tlskey_set(of_tlskey_t key, void *ptr) +{ + ObtainSemaphore(&semaphore); + @try { + struct Task *task = FindTask(NULL); + + if (key->table == NULL) + key->table = objc_hashtable_new(hashFunc, equalFunc, 2); + + if (ptr == NULL) + objc_hashtable_delete(key->table, task); + else + objc_hashtable_set(key->table, task, ptr); + } @catch (id e) { + return false; + } @finally { + ReleaseSemaphore(&semaphore); + } + + return true; +} + +void +of_tlskey_thread_exited(void) +{ + ObtainSemaphore(&semaphore); + @try { + struct Task *task = FindTask(NULL); + + for (of_tlskey_t iter = firstKey; iter != NULL; + iter = iter->next) + if (iter->table != NULL) + objc_hashtable_delete(iter->table, task); + } @finally { + ReleaseSemaphore(&semaphore); + } +} ADDED src/platform/libfat/OFString+PathAdditions.m Index: src/platform/libfat/OFString+PathAdditions.m ================================================================== --- src/platform/libfat/OFString+PathAdditions.m +++ src/platform/libfat/OFString+PathAdditions.m @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFString+PathAdditions.h" +#import "OFArray.h" +#import "OFFileURLHandler.h" + +#import "OFOutOfRangeException.h" + +int _OFString_PathAdditions_reference; + +@implementation OFString (PathAdditions) ++ (OFString *)pathWithComponents: (OFArray *)components +{ + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + bool first = true; + + for (OFString *component in components) { + if (component.length == 0) + continue; + + if ([component isEqual: @"/"]) + continue; + + if (!first && ![ret hasSuffix: @"/"]) + [ret appendString: @"/"]; + + [ret appendString: component]; + + first = false; + } + + if ([ret hasSuffix: @":"]) + [ret appendString: @"/"]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)isAbsolutePath +{ + return [self containsString: @":/"]; +} + +- (OFArray *)pathComponents +{ + OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + const char *cString = self.UTF8String; + size_t i, last = 0, cStringLength = self.UTF8StringLength; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return ret; + } + + for (i = 0; i < cStringLength; i++) { + if (cString[i] == '/') { + if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + + last = i + 1; + } + } + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (OFString *)lastPathComponent +{ + void *pool = objc_autoreleasePoolPush(); + const char *cString; + size_t cStringLength; + ssize_t i; + OFString *ret; + + if ([self hasSuffix: @":/"]) + return self; + + cString = self.UTF8String; + cStringLength = self.UTF8StringLength; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (cString[cStringLength - 1] == '/') + cStringLength--; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (cStringLength - 1 > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + for (i = cStringLength - 1; i >= 0; i--) { + if (cString[i] == '/') { + i++; + break; + } + } + + /* + * Only one component, but the trailing delimiter might have been + * removed, so return a new string anyway. + */ + if (i < 0) + i = 0; + + ret = [[OFString alloc] initWithUTF8String: cString + i + length: cStringLength - i]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + +- (OFString *)pathExtension +{ + void *pool = objc_autoreleasePoolPush(); + OFString *ret, *fileName; + size_t pos; + + fileName = self.lastPathComponent; + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + ret = [fileName substringWithRange: + of_range(pos + 1, fileName.length - pos - 1)]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingLastPathComponent +{ + void *pool = objc_autoreleasePoolPush(); + const char *cString; + size_t cStringLength; + OFString *ret; + + if ([self hasSuffix: @":/"]) + return self; + + cString = self.UTF8String; + cStringLength = self.UTF8StringLength; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (cString[cStringLength - 1] == '/') + cStringLength--; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + for (size_t i = cStringLength; i >= 1; i--) { + if (cString[i - 1] == '/') { + ret = [[OFString alloc] initWithUTF8String: cString + length: i - 1]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; + } + } + + objc_autoreleasePoolPop(pool); + + return @"."; +} + +- (OFString *)stringByDeletingPathExtension +{ + void *pool; + OFMutableArray OF_GENERIC(OFString *) *components; + OFString *ret, *fileName; + size_t pos; + + if (self.length == 0) + return [[self copy] autorelease]; + + pool = objc_autoreleasePoolPush(); + components = [[self.pathComponents mutableCopy] autorelease]; + fileName = components.lastObject; + + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + fileName = [fileName substringWithRange: of_range(0, pos)]; + [components replaceObjectAtIndex: components.count - 1 + withObject: fileName]; + + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByStandardizingPath +{ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components; + OFMutableArray OF_GENERIC(OFString *) *array; + OFString *ret; + bool done = false; + + if (self.length == 0) + return @""; + + components = self.pathComponents; + + if (components.count == 1) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + array = [[components mutableCopy] autorelease]; + + while (!done) { + size_t length = array.count; + + done = true; + + for (size_t i = 0; i < length; i++) { + OFString *component = [array objectAtIndex: i]; + OFString *parent = + (i > 0 ? [array objectAtIndex: i - 1] : 0); + + if ([component isEqual: @"."] || + component.length == 0) { + [array removeObjectAtIndex: i]; + + done = false; + break; + } + + if ([component isEqual: @".."] && + parent != nil && ![parent isEqual: @".."]) { + [array removeObjectsInRange: + of_range(i - 1, 2)]; + + done = false; + break; + } + } + } + + if ([self hasSuffix: @"/"]) + [array addObject: @""]; + + ret = [[array componentsJoinedByString: @"/"] retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + +- (OFString *)stringByAppendingPathComponent: (OFString *)component +{ + if ([self hasSuffix: @"/"]) + return [self stringByAppendingString: component]; + else { + OFMutableString *ret = [[self mutableCopy] autorelease]; + + [ret appendString: @"/"]; + [ret appendString: component]; + + [ret makeImmutable]; + + return ret; + } +} + +- (bool)of_isDirectoryPath +{ + return ([self hasSuffix: @"/"] || + [OFFileURLHandler of_directoryExistsAtPath: self]); +} + +- (OFString *)of_pathToURLPathWithURLEncodedHost: (OFString **)URLEncodedHost +{ + return [self stringByPrependingString: @"/"]; +} + +- (OFString *)of_URLPathToPathWithURLEncodedHost: (OFString *)URLEncodedHost +{ + OFString *path = self; + + if (path.length > 1 && [path hasSuffix: @"/"]) + path = [path substringWithRange: of_range(0, path.length - 1)]; + + return [path substringWithRange: of_range(1, path.length - 1)]; +} + +- (OFString *)of_pathComponentToURLPathComponent +{ + return self; +} +@end ADDED src/platform/posix/OFProcess.m Index: src/platform/posix/OFProcess.m ================================================================== --- src/platform/posix/OFProcess.m +++ src/platform/posix/OFProcess.m @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#include + +#ifdef HAVE_SYS_WAIT_H +# include +#endif + +#include "unistd_wrapper.h" +#ifdef HAVE_SPAWN_H +# include +#endif + +#import "OFProcess.h" +#import "OFString.h" +#import "OFArray.h" +#import "OFDictionary.h" +#import "OFLocale.h" + +#import "OFInitializationFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFWriteFailedException.h" + +#ifndef HAVE_POSIX_SPAWNP +extern char **environ; +#endif + +@interface OFProcess () +- (void)of_getArgv: (char ***)argv + forProgramName: (OFString *)programName + andArguments: (OFArray *)arguments; +- (char **)of_environmentForDictionary: (OFDictionary *)dictionary; +@end + +@implementation OFProcess ++ (instancetype)processWithProgram: (OFString *)program +{ + return [[[self alloc] initWithProgram: program] autorelease]; +} + ++ (instancetype)processWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + arguments: arguments] autorelease]; +} + ++ (instancetype)processWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments] autorelease]; +} + ++ (instancetype)processWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments + environment: environment] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithProgram: (OFString *)program +{ + return [self initWithProgram: program + programName: program + arguments: nil + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + self = [super init]; + + @try { + void *pool = objc_autoreleasePoolPush(); + const char *path; + char **argv; + + if (pipe(_readPipe) != 0 || pipe(_writePipe) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + path = [program cStringWithEncoding: [OFLocale encoding]]; + [self of_getArgv: &argv + forProgramName: programName + andArguments: arguments]; + + @try { + char **env = [self + of_environmentForDictionary: environment]; +#ifdef HAVE_POSIX_SPAWNP + posix_spawn_file_actions_t actions; + posix_spawnattr_t attr; + + if (posix_spawn_file_actions_init(&actions) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (posix_spawnattr_init(&attr) != 0) { + posix_spawn_file_actions_destroy(&actions); + + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } + + @try { + if (posix_spawn_file_actions_addclose(&actions, + _readPipe[0]) != 0 || + posix_spawn_file_actions_addclose(&actions, + _writePipe[1]) != 0 || + posix_spawn_file_actions_adddup2(&actions, + _writePipe[0], 0) != 0 || + posix_spawn_file_actions_adddup2(&actions, + _readPipe[1], 1) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + +# ifdef POSIX_SPAWN_CLOEXEC_DEFAULT + if (posix_spawnattr_setflags(&attr, + POSIX_SPAWN_CLOEXEC_DEFAULT) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; +# endif + + if (posix_spawnp(&_pid, path, &actions, &attr, + argv, env) != 0) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } @finally { + posix_spawn_file_actions_destroy(&actions); + posix_spawnattr_destroy(&attr); + } +#else + if ((_pid = vfork()) == 0) { + environ = env; + + close(_readPipe[0]); + close(_writePipe[1]); + dup2(_writePipe[0], 0); + dup2(_readPipe[1], 1); + execvp(path, argv); + + _exit(EXIT_FAILURE); + } + + if (_pid == -1) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; +#endif + } @finally { + close(_readPipe[1]); + close(_writePipe[0]); + [self freeMemory: argv]; + } + + objc_autoreleasePoolPop(pool); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_readPipe[0] != -1) + [self close]; + + [super dealloc]; +} + +- (void)of_getArgv: (char ***)argv + forProgramName: (OFString *)programName + andArguments: (OFArray *)arguments +{ + OFString *const *objects = arguments.objects; + size_t i, count = arguments.count; + of_string_encoding_t encoding; + + *argv = [self allocMemoryWithSize: sizeof(char *) + count: count + 2]; + + encoding = [OFLocale encoding]; + + (*argv)[0] = (char *)[programName cStringWithEncoding: encoding]; + + for (i = 0; i < count; i++) + (*argv)[i + 1] = + (char *)[objects[i] cStringWithEncoding: encoding]; + + (*argv)[i + 1] = NULL; +} + +- (char **)of_environmentForDictionary: (OFDictionary *)environment +{ + OFEnumerator *keyEnumerator, *objectEnumerator; + char **envp; + size_t i, count; + of_string_encoding_t encoding; + + if (environment == nil) + return NULL; + + encoding = [OFLocale encoding]; + + count = environment.count; + envp = [self allocMemoryWithSize: sizeof(char *) + count: count + 1]; + + keyEnumerator = [environment keyEnumerator]; + objectEnumerator = [environment objectEnumerator]; + + for (i = 0; i < count; i++) { + OFString *key; + OFString *object; + size_t keyLen, objectLen; + + key = [keyEnumerator nextObject]; + object = [objectEnumerator nextObject]; + + keyLen = [key cStringLengthWithEncoding: encoding]; + objectLen = [object cStringLengthWithEncoding: encoding]; + + envp[i] = [self allocMemoryWithSize: keyLen + objectLen + 2]; + + memcpy(envp[i], [key cStringWithEncoding: encoding], keyLen); + envp[i][keyLen] = '='; + memcpy(envp[i] + keyLen + 1, + [object cStringWithEncoding: encoding], objectLen); + envp[i][keyLen + objectLen + 1] = '\0'; + } + + envp[i] = NULL; + + return envp; +} + +- (bool)lowlevelIsAtEndOfStream +{ + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + return _atEndOfStream; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer + length: (size_t)length +{ + ssize_t ret; + + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + if ((ret = read(_readPipe[0], buffer, length)) < 0) + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: length + errNo: errno]; + + if (ret == 0) + _atEndOfStream = true; + + return ret; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer + length: (size_t)length +{ + ssize_t bytesWritten; + + if (_writePipe[1] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (length > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + if ((bytesWritten = write(_writePipe[1], buffer, length)) < 0) + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: errno]; + + return (size_t)bytesWritten; +} + +- (int)fileDescriptorForReading +{ + return _readPipe[0]; +} + +- (int)fileDescriptorForWriting +{ + return _writePipe[1]; +} + +- (void)closeForWriting +{ + if (_writePipe[1] != -1) + close(_writePipe[1]); + + _writePipe[1] = -1; +} + +- (void)close +{ + if (_readPipe[0] == -1) + @throw [OFNotOpenException exceptionWithObject: self]; + + [self closeForWriting]; + close(_readPipe[0]); + + if (_pid != -1) { + kill(_pid, SIGTERM); + waitpid(_pid, &_status, WNOHANG); + } + + _pid = -1; + _readPipe[0] = -1; + + [super close]; +} +@end ADDED src/platform/posix/OFString+PathAdditions.m Index: src/platform/posix/OFString+PathAdditions.m ================================================================== --- src/platform/posix/OFString+PathAdditions.m +++ src/platform/posix/OFString+PathAdditions.m @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFString+PathAdditions.h" +#import "OFArray.h" +#import "OFFileURLHandler.h" + +#import "OFOutOfRangeException.h" + +int _OFString_PathAdditions_reference; + +@implementation OFString (PathAdditions) ++ (OFString *)pathWithComponents: (OFArray *)components +{ + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + bool first = true; + + for (OFString *component in components) { + if (component.length == 0) + continue; + + if (!first && [component isEqual: @"/"]) + continue; + + if (!first && ![ret hasSuffix: @"/"]) + [ret appendString: @"/"]; + + [ret appendString: component]; + + first = false; + } + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)isAbsolutePath +{ + return [self hasPrefix: @"/"]; +} + +- (OFArray *)pathComponents +{ + OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + const char *cString = self.UTF8String; + size_t i, last = 0, cStringLength = self.UTF8StringLength; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return ret; + } + + for (i = 0; i < cStringLength; i++) { + if (cString[i] == '/') { + if (i == 0) + [ret addObject: @"/"]; + else if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + + last = i + 1; + } + } + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (OFString *)lastPathComponent +{ + void *pool = objc_autoreleasePoolPush(); + const char *cString = self.UTF8String; + size_t cStringLength = self.UTF8StringLength; + ssize_t i; + OFString *ret; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (cString[cStringLength - 1] == '/') + cStringLength--; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @"/"; + } + + if (cStringLength - 1 > SSIZE_MAX) + @throw [OFOutOfRangeException exception]; + + for (i = cStringLength - 1; i >= 0; i--) { + if (cString[i] == '/') { + i++; + break; + } + } + + /* + * Only one component, but the trailing delimiter might have been + * removed, so return a new string anyway. + */ + if (i < 0) + i = 0; + + ret = [[OFString alloc] initWithUTF8String: cString + i + length: cStringLength - i]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + +- (OFString *)pathExtension +{ + void *pool = objc_autoreleasePoolPush(); + OFString *ret, *fileName; + size_t pos; + + fileName = self.lastPathComponent; + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + ret = [fileName substringWithRange: + of_range(pos + 1, fileName.length - pos - 1)]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingLastPathComponent +{ + void *pool = objc_autoreleasePoolPush(); + const char *cString = self.UTF8String; + size_t cStringLength = self.UTF8StringLength; + OFString *ret; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (cString[cStringLength - 1] == '/') + cStringLength--; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return @"/"; + } + + for (size_t i = cStringLength; i >= 1; i--) { + if (cString[i - 1] == '/') { + if (i == 1) { + objc_autoreleasePoolPop(pool); + return @"/"; + } + + ret = [[OFString alloc] initWithUTF8String: cString + length: i - 1]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; + } + } + + objc_autoreleasePoolPop(pool); + + return @"."; +} + +- (OFString *)stringByDeletingPathExtension +{ + void *pool; + OFMutableArray OF_GENERIC(OFString *) *components; + OFString *ret, *fileName; + size_t pos; + + if (self.length == 0) + return [[self copy] autorelease]; + + pool = objc_autoreleasePoolPush(); + components = [[self.pathComponents mutableCopy] autorelease]; + fileName = components.lastObject; + + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + fileName = [fileName substringWithRange: of_range(0, pos)]; + [components replaceObjectAtIndex: [components count] - 1 + withObject: fileName]; + + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByStandardizingPath +{ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components; + OFMutableArray OF_GENERIC(OFString *) *array; + OFString *ret; + bool done = false, startsWithSlash; + + if (self.length == 0) + return @""; + + components = self.pathComponents; + + if (components.count == 1) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + array = [[components mutableCopy] autorelease]; + startsWithSlash = [self hasPrefix: @"/"]; + + if (startsWithSlash) + [array removeObjectAtIndex: 0]; + + while (!done) { + size_t length = array.count; + + done = true; + + for (size_t i = 0; i < length; i++) { + OFString *component = [array objectAtIndex: i]; + OFString *parent = + (i > 0 ? [array objectAtIndex: i - 1] : 0); + + if ([component isEqual: @"."] || + component.length == 0) { + [array removeObjectAtIndex: i]; + + done = false; + break; + } + + if ([component isEqual: @".."] && + parent != nil && ![parent isEqual: @".."]) { + [array removeObjectsInRange: + of_range(i - 1, 2)]; + + done = false; + break; + } + } + } + + if (startsWithSlash) + [array insertObject: @"" + atIndex: 0]; + + if ([self hasSuffix: @"/"]) + [array addObject: @""]; + + ret = [[array componentsJoinedByString: @"/"] retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + +- (OFString *)stringByAppendingPathComponent: (OFString *)component +{ + if ([self hasSuffix: @"/"]) + return [self stringByAppendingString: component]; + else { + OFMutableString *ret = [[self mutableCopy] autorelease]; + + [ret appendString: @"/"]; + [ret appendString: component]; + + [ret makeImmutable]; + + return ret; + } +} + +- (bool)of_isDirectoryPath +{ + return ([self hasSuffix: @"/"] || + [OFFileURLHandler of_directoryExistsAtPath: self]); +} + +- (OFString *)of_pathToURLPathWithURLEncodedHost: (OFString **)URLEncodedHost +{ + return self; +} + +- (OFString *)of_URLPathToPathWithURLEncodedHost: (OFString *)URLEncodedHost +{ + OFString *path = self; + + if (path.length > 1 && [path hasSuffix: @"/"]) + path = [path substringWithRange: of_range(0, path.length - 1)]; + + return path; +} + +- (OFString *)of_pathComponentToURLPathComponent +{ + return self; +} +@end ADDED src/platform/posix/condition.m Index: src/platform/posix/condition.m ================================================================== --- src/platform/posix/condition.m +++ src/platform/posix/condition.m @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "condition.h" + +bool +of_condition_new(of_condition_t *condition) +{ + return (pthread_cond_init(condition, NULL) == 0); +} + +bool +of_condition_signal(of_condition_t *condition) +{ + return (pthread_cond_signal(condition) == 0); +} + +bool +of_condition_broadcast(of_condition_t *condition) +{ + return (pthread_cond_broadcast(condition) == 0); +} + +bool +of_condition_wait(of_condition_t *condition, of_mutex_t *mutex) +{ + return (pthread_cond_wait(condition, mutex) == 0); +} + +bool +of_condition_timed_wait(of_condition_t *condition, of_mutex_t *mutex, + of_time_interval_t timeout) +{ + struct timespec ts; + + ts.tv_sec = (time_t)timeout; + ts.tv_nsec = (timeout - ts.tv_sec) * 1000000000; + + return (pthread_cond_timedwait(condition, mutex, &ts) == 0); +} + +bool +of_condition_free(of_condition_t *condition) +{ + return (pthread_cond_destroy(condition) == 0); +} ADDED src/platform/posix/mutex.m Index: src/platform/posix/mutex.m ================================================================== --- src/platform/posix/mutex.m +++ src/platform/posix/mutex.m @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "mutex.h" + +bool +of_mutex_new(of_mutex_t *mutex) +{ + return (pthread_mutex_init(mutex, NULL) == 0); +} + +bool +of_mutex_lock(of_mutex_t *mutex) +{ + return (pthread_mutex_lock(mutex) == 0); +} + +bool +of_mutex_trylock(of_mutex_t *mutex) +{ + return (pthread_mutex_trylock(mutex) == 0); +} + +bool +of_mutex_unlock(of_mutex_t *mutex) +{ + return (pthread_mutex_unlock(mutex) == 0); +} + +bool +of_mutex_free(of_mutex_t *mutex) +{ + return (pthread_mutex_destroy(mutex) == 0); +} + +#ifdef OF_HAVE_RECURSIVE_PTHREAD_MUTEXES +bool +of_rmutex_new(of_rmutex_t *rmutex) +{ + pthread_mutexattr_t attr; + + if (pthread_mutexattr_init(&attr) != 0) + return false; + + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) + return false; + + if (pthread_mutex_init(rmutex, &attr) != 0) + return false; + + if (pthread_mutexattr_destroy(&attr) != 0) + return false; + + return true; +} + +bool +of_rmutex_lock(of_rmutex_t *rmutex) +{ + return of_mutex_lock(rmutex); +} + +bool +of_rmutex_trylock(of_rmutex_t *rmutex) +{ + return of_mutex_trylock(rmutex); +} + +bool +of_rmutex_unlock(of_rmutex_t *rmutex) +{ + return of_mutex_unlock(rmutex); +} + +bool +of_rmutex_free(of_rmutex_t *rmutex) +{ + return of_mutex_free(rmutex); +} +#else +bool +of_rmutex_new(of_rmutex_t *rmutex) +{ + if (!of_mutex_new(&rmutex->mutex)) + return false; + + if (!of_tlskey_new(&rmutex->count)) + return false; + + return true; +} + +bool +of_rmutex_lock(of_rmutex_t *rmutex) +{ + uintptr_t count = (uintptr_t)of_tlskey_get(rmutex->count); + + if (count > 0) { + if (!of_tlskey_set(rmutex->count, (void *)(count + 1))) + return false; + + return true; + } + + if (!of_mutex_lock(&rmutex->mutex)) + return false; + + if (!of_tlskey_set(rmutex->count, (void *)1)) { + of_mutex_unlock(&rmutex->mutex); + return false; + } + + return true; +} + +bool +of_rmutex_trylock(of_rmutex_t *rmutex) +{ + uintptr_t count = (uintptr_t)of_tlskey_get(rmutex->count); + + if (count > 0) { + if (!of_tlskey_set(rmutex->count, (void *)(count + 1))) + return false; + + return true; + } + + if (!of_mutex_trylock(&rmutex->mutex)) + return false; + + if (!of_tlskey_set(rmutex->count, (void *)1)) { + of_mutex_unlock(&rmutex->mutex); + return false; + } + + return true; +} + +bool +of_rmutex_unlock(of_rmutex_t *rmutex) +{ + uintptr_t count = (uintptr_t)of_tlskey_get(rmutex->count); + + if (count > 1) { + if (!of_tlskey_set(rmutex->count, (void *)(count - 1))) + return false; + + return true; + } + + if (!of_tlskey_set(rmutex->count, (void *)0)) + return false; + + if (!of_mutex_unlock(&rmutex->mutex)) + return false; + + return true; +} + +bool +of_rmutex_free(of_rmutex_t *rmutex) +{ + if (!of_mutex_free(&rmutex->mutex)) + return false; + + if (!of_tlskey_free(rmutex->count)) + return false; + + return true; +} +#endif ADDED src/platform/posix/thread.m Index: src/platform/posix/thread.m ================================================================== --- src/platform/posix/thread.m +++ src/platform/posix/thread.m @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#ifdef HAVE_PTHREAD_NP_H +# include +#endif + +#ifdef OF_HAIKU +# include +#endif + +#import "thread.h" +#import "macros.h" + +static int minPrio = 0, maxPrio = 0, normalPrio = 0; + +struct thread_ctx { + void (*function)(id object); + id object; + const char *name; +}; + +/* + * This is done here to make sure this is done as early as possible in the main + * thread. + */ +OF_CONSTRUCTOR() +{ + pthread_attr_t pattr; + + if (pthread_attr_init(&pattr) == 0) { +#ifdef HAVE_PTHREAD_ATTR_GETSCHEDPOLICY + int policy; +#endif + struct sched_param param; + +#ifdef HAVE_PTHREAD_ATTR_GETSCHEDPOLICY + if (pthread_attr_getschedpolicy(&pattr, &policy) == 0) { + minPrio = sched_get_priority_min(policy); + maxPrio = sched_get_priority_max(policy); + + if (minPrio == -1 || maxPrio == -1) + minPrio = maxPrio = 0; + } + + if (pthread_attr_getschedparam(&pattr, ¶m) != 0) + normalPrio = param.sched_priority; + else + minPrio = maxPrio = 0; + + pthread_attr_destroy(&pattr); +#endif + } +} + +static void * +functionWrapper(void *data) +{ + struct thread_ctx *ctx = data; + + if (ctx->name != NULL) + of_thread_set_name(ctx->name); + + pthread_cleanup_push(free, data); + + ctx->function(ctx->object); + + pthread_cleanup_pop(1); + return NULL; +} + +bool +of_thread_attr_init(of_thread_attr_t *attr) +{ + pthread_attr_t pattr; + + if (pthread_attr_init(&pattr) != 0) + return false; + + @try { + attr->priority = 0; + + if (pthread_attr_getstacksize(&pattr, &attr->stackSize) != 0) + return false; + } @finally { + pthread_attr_destroy(&pattr); + } + + return true; +} + +bool +of_thread_new(of_thread_t *thread, const char *name, void (*function)(id), + id object, const of_thread_attr_t *attr) +{ + bool ret; + pthread_attr_t pattr; + + if (pthread_attr_init(&pattr) != 0) + return false; + + @try { + struct thread_ctx *ctx; + + if (attr != NULL) { + struct sched_param param; + + if (attr->priority < -1 || attr->priority > 1) { + errno = EINVAL; + return false; + } + +#ifdef HAVE_PTHREAD_ATTR_SETINHERITSCHED + if (pthread_attr_setinheritsched(&pattr, + PTHREAD_EXPLICIT_SCHED) != 0) + return false; +#endif + + if (attr->priority < 0) { + param.sched_priority = minPrio + + (1.0f + attr->priority) * + (normalPrio - minPrio); + } else + param.sched_priority = normalPrio + + attr->priority * (maxPrio - normalPrio); + + if (pthread_attr_setschedparam(&pattr, ¶m) != 0) + return false; + + if (attr->stackSize > 0) { + if (pthread_attr_setstacksize(&pattr, + attr->stackSize) != 0) + return false; + } + } + + if ((ctx = malloc(sizeof(*ctx))) == NULL) { + errno = ENOMEM; + return false; + } + + ctx->function = function; + ctx->object = object; + ctx->name = name; + + ret = (pthread_create(thread, &pattr, + functionWrapper, ctx) == 0); + } @finally { + pthread_attr_destroy(&pattr); + } + + return ret; +} + +bool +of_thread_join(of_thread_t thread) +{ + void *ret; + + return (pthread_join(thread, &ret) == 0); +} + +bool +of_thread_detach(of_thread_t thread) +{ + return (pthread_detach(thread) == 0); +} + +void +of_thread_set_name(const char *name) +{ +#if defined(OF_HAIKU) + rename_thread(find_thread(NULL), name); +#elif defined(HAVE_PTHREAD_SET_NAME_NP) + pthread_set_name_np(pthread_self(), name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) +# if defined(OF_MACOS) || defined(OF_IOS) + pthread_setname_np(name); +# elif defined(__GLIBC__) + char buffer[16]; + + strncpy(buffer, name, 15); + buffer[15] = 0; + + pthread_setname_np(pthread_self(), buffer); +# endif +#endif +} ADDED src/platform/posix/tlskey.m Index: src/platform/posix/tlskey.m ================================================================== --- src/platform/posix/tlskey.m +++ src/platform/posix/tlskey.m @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "tlskey.h" + +bool +of_tlskey_new(of_tlskey_t *key) +{ + return (pthread_key_create(key, NULL) == 0); +} + +bool +of_tlskey_free(of_tlskey_t key) +{ + return (pthread_key_delete(key) == 0); +} ADDED src/platform/windows/OFProcess.m Index: src/platform/windows/OFProcess.m ================================================================== --- src/platform/windows/OFProcess.m +++ src/platform/windows/OFProcess.m @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include +#include + +#import "OFProcess.h" +#import "OFString.h" +#import "OFArray.h" +#import "OFDictionary.h" +#import "OFData.h" + +#import "OFInitializationFailedException.h" +#import "OFNotOpenException.h" +#import "OFOutOfRangeException.h" +#import "OFReadFailedException.h" +#import "OFWriteFailedException.h" + +#include + +@interface OFProcess () +- (of_char16_t *)of_environmentForDictionary: (OFDictionary *)dictionary; +@end + +@implementation OFProcess ++ (instancetype)processWithProgram: (OFString *)program +{ + return [[[self alloc] initWithProgram: program] autorelease]; +} + ++ (instancetype)processWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + arguments: arguments] autorelease]; +} + ++ (instancetype)processWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments] autorelease]; +} + ++ (instancetype)processWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + return [[[self alloc] initWithProgram: program + programName: programName + arguments: arguments + environment: environment] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithProgram: (OFString *)program +{ + return [self initWithProgram: program + programName: program + arguments: nil + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments +{ + return [self initWithProgram: program + programName: program + arguments: arguments + environment: nil]; +} + +- (instancetype)initWithProgram: (OFString *)program + programName: (OFString *)programName + arguments: (OFArray *)arguments + environment: (OFDictionary *)environment +{ + self = [super init]; + + @try { + SECURITY_ATTRIBUTES sa; + PROCESS_INFORMATION pi; + STARTUPINFOW si; + void *pool; + OFMutableString *argumentsString; + of_char16_t *argumentsCopy; + size_t length; + + sa.nLength = sizeof(sa); + sa.bInheritHandle = TRUE; + sa.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&_readPipe[0], &_readPipe[1], &sa, 0)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (!SetHandleInformation(_readPipe[0], HANDLE_FLAG_INHERIT, 0)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (!CreatePipe(&_writePipe[0], &_writePipe[1], &sa, 0)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + if (!SetHandleInformation(_writePipe[1], + HANDLE_FLAG_INHERIT, 0)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + + memset(&pi, 0, sizeof(pi)); + memset(&si, 0, sizeof(si)); + + si.cb = sizeof(si); + si.hStdInput = _writePipe[0]; + si.hStdOutput = _readPipe[1]; + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + si.dwFlags |= STARTF_USESTDHANDLES; + + pool = objc_autoreleasePoolPush(); + + argumentsString = + [OFMutableString stringWithString: programName]; + [argumentsString replaceOccurrencesOfString: @"\\\"" + withString: @"\\\\\""]; + [argumentsString replaceOccurrencesOfString: @"\"" + withString: @"\\\""]; + + if ([argumentsString containsString: @" "]) { + [argumentsString prependString: @"\""]; + [argumentsString appendString: @"\""]; + } + + for (OFString *argument in arguments) { + OFMutableString *tmp = + [[argument mutableCopy] autorelease]; + bool containsSpaces = [tmp containsString: @" "]; + + [argumentsString appendString: @" "]; + + if (containsSpaces) + [argumentsString appendString: @"\""]; + + [tmp replaceOccurrencesOfString: @"\\\"" + withString: @"\\\\\""]; + [tmp replaceOccurrencesOfString: @"\"" + withString: @"\\\""]; + + [argumentsString appendString: tmp]; + + if (containsSpaces) + [argumentsString appendString: @"\""]; + } + + length = argumentsString.UTF16StringLength; + argumentsCopy = [self allocMemoryWithSize: sizeof(of_char16_t) + count: length + 1]; + memcpy(argumentsCopy, argumentsString.UTF16String, + (argumentsString.UTF16StringLength + 1) * 2); + @try { + if (!CreateProcessW(program.UTF16String, + argumentsCopy, NULL, NULL, TRUE, + CREATE_UNICODE_ENVIRONMENT, + [self of_environmentForDictionary: environment], + NULL, &si, &pi)) + @throw [OFInitializationFailedException + exceptionWithClass: self.class]; + } @finally { + [self freeMemory: argumentsCopy]; + } + + objc_autoreleasePoolPop(pool); + + _process = pi.hProcess; + CloseHandle(pi.hThread); + + CloseHandle(_readPipe[1]); + CloseHandle(_writePipe[0]); + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + if (_readPipe[0] != NULL) + [self close]; + + [super dealloc]; +} + +- (of_char16_t *)of_environmentForDictionary: (OFDictionary *)environment +{ + OFMutableData *env; + OFEnumerator *keyEnumerator, *objectEnumerator; + OFString *key, *object; + const of_char16_t equal = '='; + const of_char16_t zero[2] = { 0, 0 }; + + if (environment == nil) + return NULL; + + env = [OFMutableData dataWithItemSize: sizeof(of_char16_t)]; + + keyEnumerator = [environment keyEnumerator]; + objectEnumerator = [environment objectEnumerator]; + while ((key = [keyEnumerator nextObject]) != nil && + (object = [objectEnumerator nextObject]) != nil) { + [env addItems: key.UTF16String + count: key.UTF16StringLength]; + [env addItems: &equal + count: 1]; + [env addItems: object.UTF16String + count: object.UTF16StringLength]; + [env addItems: &zero + count: 1]; + } + [env addItems: zero + count: 2]; + + return env.mutableItems; +} + +- (bool)lowlevelIsAtEndOfStream +{ + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + return _atEndOfStream; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer + length: (size_t)length +{ + DWORD ret; + + if (length > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (!ReadFile(_readPipe[0], buffer, (DWORD)length, &ret, NULL)) { + if (GetLastError() == ERROR_BROKEN_PIPE) { + _atEndOfStream = true; + return 0; + } + + @throw [OFReadFailedException exceptionWithObject: self + requestedLength: length + errNo: EIO]; + } + + if (ret == 0) + _atEndOfStream = true; + + return ret; +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer + length: (size_t)length +{ + DWORD bytesWritten; + + if (length > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + if (_writePipe[1] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + if (!WriteFile(_writePipe[1], buffer, (DWORD)length, &bytesWritten, + NULL)) { + int errNo = EIO; + + if (GetLastError() == ERROR_BROKEN_PIPE) + errNo = EPIPE; + + @throw [OFWriteFailedException exceptionWithObject: self + requestedLength: length + bytesWritten: 0 + errNo: errNo]; + } + + return (size_t)bytesWritten; +} + +- (void)closeForWriting +{ + if (_writePipe[1] != NULL) + CloseHandle(_writePipe[1]); + + _writePipe[1] = NULL; +} + +- (void)close +{ + if (_readPipe[0] == NULL) + @throw [OFNotOpenException exceptionWithObject: self]; + + [self closeForWriting]; + CloseHandle(_readPipe[0]); + + if (_process != INVALID_HANDLE_VALUE) { + TerminateProcess(_process, 0); + CloseHandle(_process); + } + + _process = INVALID_HANDLE_VALUE; + _readPipe[0] = NULL; + + [super close]; +} +@end ADDED src/platform/windows/OFString+PathAdditions.m Index: src/platform/windows/OFString+PathAdditions.m ================================================================== --- src/platform/windows/OFString+PathAdditions.m +++ src/platform/windows/OFString+PathAdditions.m @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +/* + * This file is also used for MS-DOS! Don't forget to #ifdef Windows-specific + * parts! + */ + +#include "config.h" + +#import "OFString+PathAdditions.h" +#import "OFArray.h" +#import "OFFileURLHandler.h" +#import "OFURL.h" + +#import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" + +int _OFString_PathAdditions_reference; + +@implementation OFString (PathAdditions) ++ (OFString *)pathWithComponents: (OFArray *)components +{ + OFMutableString *ret = [OFMutableString string]; + void *pool = objc_autoreleasePoolPush(); + bool first = true; + + for (OFString *component in components) { + if (component.length == 0) + continue; + + if (!first && ![ret hasSuffix: @":"] && + ([component isEqual: @"\\"] || [component isEqual: @"/"])) + continue; + + if (!first && ![ret hasSuffix: @"\\"] && + ![ret hasSuffix: @"/"] && ![ret hasSuffix: @":"]) + [ret appendString: @"\\"]; + + [ret appendString: component]; + + first = false; + } + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (bool)isAbsolutePath +{ +#ifdef OF_WINDOWS + if ([self hasPrefix: @"\\\\"]) + return true; +#endif + + return ([self containsString: @":\\"] || [self containsString: @":/"]); +} + +- (OFArray *)pathComponents +{ + OFMutableArray OF_GENERIC(OFString *) *ret = [OFMutableArray array]; + void *pool = objc_autoreleasePoolPush(); + const char *cString = self.UTF8String; + size_t i, last = 0, cStringLength = self.UTF8StringLength; + bool isUNC = false; + + if (cStringLength == 0) { + objc_autoreleasePoolPop(pool); + return ret; + } + +#ifdef OF_WINDOWS + if ([self hasPrefix: @"\\\\"]) { + isUNC = true; + [ret addObject: @"\\\\"]; + + cString += 2; + cStringLength -= 2; + } +#endif + + for (i = 0; i < cStringLength; i++) { + if (cString[i] == '\\' || cString[i] == '/') { + if (i == 0) + [ret addObject: [OFString + stringWithUTF8String: cString + length: 1]]; + else if (i - last != 0) + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last]]; + + last = i + 1; + } else if (!isUNC && cString[i] == ':') { + if (i + 1 < cStringLength && + (cString[i + 1] == '\\' || cString[i + 1] == '/')) + i++; + + [ret addObject: [OFString + stringWithUTF8String: cString + last + length: i - last + 1]]; + + last = i + 1; + } + } + if (i - last != 0) + [ret addObject: [OFString stringWithUTF8String: cString + last + length: i - last]]; + + [ret makeImmutable]; + + objc_autoreleasePoolPop(pool); + + return ret; +} + +- (OFString *)lastPathComponent +{ + /* + * Windows/DOS need the full parsing to determine the last path + * component. This could be optimized by not creating the temporary + * objects, though. + */ + void *pool = objc_autoreleasePoolPush(); + OFString *ret = self.pathComponents.lastObject; + + if (ret == nil) { + objc_autoreleasePoolPop(pool); + return @""; + } + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)pathExtension +{ + void *pool = objc_autoreleasePoolPush(); + OFString *ret, *fileName; + size_t pos; + + fileName = self.lastPathComponent; + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + ret = [fileName substringWithRange: + of_range(pos + 1, fileName.length - pos - 1)]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingLastPathComponent +{ + /* + * Windows/DOS need the full parsing to delete the last path component. + * This could be optimized, though. + */ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components = self.pathComponents; + size_t count = components.count; + OFString *ret; + + if (count == 0) { + objc_autoreleasePoolPop(pool); + return @""; + } + + if (count == 1) { + OFString *firstComponent = components.firstObject; + + if ([firstComponent hasSuffix: @":"] || + [firstComponent hasSuffix: @":\\"] || + [firstComponent hasSuffix: @":/"] || + [firstComponent hasPrefix: @"\\"]) { + ret = [firstComponent retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; + } + + objc_autoreleasePoolPop(pool); + return @"."; + } + + components = [components objectsInRange: + of_range(0, components.count - 1)]; + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByDeletingPathExtension +{ + void *pool; + OFMutableArray OF_GENERIC(OFString *) *components; + OFString *ret, *fileName; + size_t pos; + + if (self.length == 0) + return [[self copy] autorelease]; + + pool = objc_autoreleasePoolPush(); + components = [[self.pathComponents mutableCopy] autorelease]; + fileName = components.lastObject; + + pos = [fileName rangeOfString: @"." + options: OF_STRING_SEARCH_BACKWARDS].location; + if (pos == OF_NOT_FOUND || pos == 0) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + fileName = [fileName substringWithRange: of_range(0, pos)]; + [components replaceObjectAtIndex: components.count - 1 + withObject: fileName]; + + ret = [OFString pathWithComponents: components]; + + [ret retain]; + objc_autoreleasePoolPop(pool); + return [ret autorelease]; +} + +- (OFString *)stringByStandardizingPath +{ + void *pool = objc_autoreleasePoolPush(); + OFArray OF_GENERIC(OFString *) *components; + OFMutableArray OF_GENERIC(OFString *) *array; + OFString *ret; + bool done = false; + + if (self.length == 0) + return @""; + + components = self.pathComponents; + + if (components.count == 1) { + objc_autoreleasePoolPop(pool); + return [[self copy] autorelease]; + } + + array = [[components mutableCopy] autorelease]; + + while (!done) { + size_t length = array.count; + + done = true; + + for (size_t i = 0; i < length; i++) { + OFString *component = [array objectAtIndex: i]; + OFString *parent = + (i > 0 ? [array objectAtIndex: i - 1] : 0); + + if ([component isEqual: @"."] || + component.length == 0) { + [array removeObjectAtIndex: i]; + + done = false; + break; + } + + if ([component isEqual: @".."] && parent != nil && + ![parent isEqual: @".."] && + ![parent hasSuffix: @":"] && + ![parent hasSuffix: @":\\"] && + ![parent hasSuffix: @"://"] && + (![parent hasPrefix: @"\\"] || i != 1)) { + [array removeObjectsInRange: + of_range(i - 1, 2)]; + + done = false; + break; + } + } + } + + ret = [[OFString pathWithComponents: array] retain]; + + objc_autoreleasePoolPop(pool); + + return [ret autorelease]; +} + +- (OFString *)stringByAppendingPathComponent: (OFString *)component +{ + if ([self hasSuffix: @"\\"] || [self hasSuffix: @"/"]) + return [self stringByAppendingString: component]; + else { + OFMutableString *ret = [[self mutableCopy] autorelease]; + + [ret appendString: @"\\"]; + [ret appendString: component]; + + [ret makeImmutable]; + + return ret; + } +} + +- (bool)of_isDirectoryPath +{ + return ([self hasSuffix: @"\\"] || [self hasSuffix: @"/"] || + [OFFileURLHandler of_directoryExistsAtPath: self]); +} + +- (OFString *)of_pathToURLPathWithURLEncodedHost: (OFString **)URLEncodedHost +{ + OFString *path = self; + + if ([path hasPrefix: @"\\\\"]) { + OFArray *components = path.pathComponents; + + if (components.count < 2) + @throw [OFInvalidFormatException exception]; + + *URLEncodedHost = [[components objectAtIndex: 1] + stringByURLEncodingWithAllowedCharacters: + [OFCharacterSet URLHostAllowedCharacterSet]]; + path = [OFString pathWithComponents: [components + objectsInRange: of_range(2, components.count - 2)]]; + } + + path = [path stringByReplacingOccurrencesOfString: @"\\" + withString: @"/"]; + path = [path stringByPrependingString: @"/"]; + + return path; +} + +- (OFString *)of_URLPathToPathWithURLEncodedHost: (OFString *)URLEncodedHost +{ + OFString *path = self; + + if (path.length > 1 && [path hasSuffix: @"/"] && + ![path hasSuffix: @":/"]) + path = [path substringWithRange: of_range(0, path.length - 1)]; + + path = [path substringWithRange: of_range(1, path.length - 1)]; + path = [path stringByReplacingOccurrencesOfString: @"/" + withString: @"\\"]; + + if (URLEncodedHost != nil) { + OFString *host = [URLEncodedHost stringByURLDecoding]; + + if (path.length == 0) + path = [OFString stringWithFormat: @"\\\\%@", host]; + else + path = [OFString stringWithFormat: @"\\\\%@\\%@", + host, path]; + } + + return path; +} + +- (OFString *)of_pathComponentToURLPathComponent +{ + return [self stringByReplacingOccurrencesOfString: @"\\" + withString: @"/"]; +} +@end ADDED src/platform/windows/condition.m Index: src/platform/windows/condition.m ================================================================== --- src/platform/windows/condition.m +++ src/platform/windows/condition.m @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "condition.h" + +#include + +bool +of_condition_new(of_condition_t *condition) +{ + condition->count = 0; + + if ((condition->event = CreateEvent(NULL, FALSE, 0, NULL)) == NULL) { + errno = EAGAIN; + return false; + } + + return true; +} + +bool +of_condition_signal(of_condition_t *condition) +{ + if (!SetEvent(condition->event)) { + switch (GetLastError()) { + case ERROR_INVALID_HANDLE: + errno = EINVAL; + return false; + default: + OF_ENSURE(0); + } + } + + return true; +} + +bool +of_condition_broadcast(of_condition_t *condition) +{ + int count = condition->count; + + for (int i = 0; i < count; i++) { + if (!SetEvent(condition->event)) { + switch (GetLastError()) { + case ERROR_INVALID_HANDLE: + errno = EINVAL; + return false; + default: + OF_ENSURE(0); + } + } + } + + return true; +} + +bool +of_condition_wait(of_condition_t *condition, of_mutex_t *mutex) +{ + DWORD status; + + if (!of_mutex_unlock(mutex)) + return false; + + of_atomic_int_inc(&condition->count); + status = WaitForSingleObject(condition->event, INFINITE); + of_atomic_int_dec(&condition->count); + + switch (status) { + case WAIT_OBJECT_0: + return of_mutex_lock(mutex); + case WAIT_FAILED: + switch (GetLastError()) { + case ERROR_INVALID_HANDLE: + errno = EINVAL; + return false; + default: + OF_ENSURE(0); + } + default: + OF_ENSURE(0); + } +} + +bool +of_condition_timed_wait(of_condition_t *condition, of_mutex_t *mutex, + of_time_interval_t timeout) +{ + DWORD status; + + if (!of_mutex_unlock(mutex)) + return false; + + of_atomic_int_inc(&condition->count); + status = WaitForSingleObject(condition->event, timeout * 1000); + of_atomic_int_dec(&condition->count); + + switch (status) { + case WAIT_OBJECT_0: + return of_mutex_lock(mutex); + case WAIT_TIMEOUT: + errno = ETIMEDOUT; + return false; + case WAIT_FAILED: + switch (GetLastError()) { + case ERROR_INVALID_HANDLE: + errno = EINVAL; + return false; + default: + OF_ENSURE(0); + } + default: + OF_ENSURE(0); + } +} + +bool +of_condition_free(of_condition_t *condition) +{ + if (condition->count != 0) { + errno = EBUSY; + return false; + } + + return CloseHandle(condition->event); +} ADDED src/platform/windows/mutex.m Index: src/platform/windows/mutex.m ================================================================== --- src/platform/windows/mutex.m +++ src/platform/windows/mutex.m @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "mutex.h" + +#include + +bool +of_mutex_new(of_mutex_t *mutex) +{ + InitializeCriticalSection(mutex); + + return true; +} + +bool +of_mutex_lock(of_mutex_t *mutex) +{ + EnterCriticalSection(mutex); + + return true; +} + +bool +of_mutex_trylock(of_mutex_t *mutex) +{ + if (!TryEnterCriticalSection(mutex)) { + errno = EBUSY; + return false; + } + + return true; +} + +bool +of_mutex_unlock(of_mutex_t *mutex) +{ + LeaveCriticalSection(mutex); + + return true; +} + +bool +of_mutex_free(of_mutex_t *mutex) +{ + DeleteCriticalSection(mutex); + + return true; +} + +bool +of_rmutex_new(of_rmutex_t *rmutex) +{ + return of_mutex_new(rmutex); +} + +bool +of_rmutex_lock(of_rmutex_t *rmutex) +{ + return of_mutex_lock(rmutex); +} + +bool +of_rmutex_trylock(of_rmutex_t *rmutex) +{ + return of_mutex_trylock(rmutex); +} + +bool +of_rmutex_unlock(of_rmutex_t *rmutex) +{ + return of_mutex_unlock(rmutex); +} + +bool +of_rmutex_free(of_rmutex_t *rmutex) +{ + return of_mutex_free(rmutex); +} ADDED src/platform/windows/thread.m Index: src/platform/windows/thread.m ================================================================== --- src/platform/windows/thread.m +++ src/platform/windows/thread.m @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#include + +#import "thread.h" +#import "macros.h" + +#include + +bool +of_thread_attr_init(of_thread_attr_t *attr) +{ + attr->priority = 0; + attr->stackSize = 0; + + return true; +} + +bool +of_thread_new(of_thread_t *thread, const char *name, void (*function)(id), + id object, const of_thread_attr_t *attr) +{ + *thread = CreateThread(NULL, (attr != NULL ? attr->stackSize : 0), + (LPTHREAD_START_ROUTINE)function, (void *)object, 0, NULL); + + if (thread == NULL) { + switch (GetLastError()) { + case ERROR_NOT_ENOUGH_MEMORY: + errno = ENOMEM; + return false; + case ERROR_ACCESS_DENIED: + errno = EACCES; + return false; + default: + OF_ENSURE(0); + } + } + + if (attr != NULL && attr->priority != 0) { + DWORD priority; + + if (attr->priority < -1 || attr->priority > 1) { + errno = EINVAL; + return false; + } + + if (attr->priority < 0) + priority = THREAD_PRIORITY_LOWEST + + (1.0 + attr->priority) * + (THREAD_PRIORITY_NORMAL - THREAD_PRIORITY_LOWEST); + else + priority = THREAD_PRIORITY_NORMAL + + attr->priority * + (THREAD_PRIORITY_HIGHEST - THREAD_PRIORITY_NORMAL); + + OF_ENSURE(!SetThreadPriority(*thread, priority)); + } + + return true; +} + +bool +of_thread_join(of_thread_t thread) +{ + switch (WaitForSingleObject(thread, INFINITE)) { + case WAIT_OBJECT_0: + CloseHandle(thread); + return true; + case WAIT_FAILED: + switch (GetLastError()) { + case ERROR_INVALID_HANDLE: + errno = EINVAL; + return false; + default: + OF_ENSURE(0); + } + default: + OF_ENSURE(0); + } +} + +bool +of_thread_detach(of_thread_t thread) +{ + CloseHandle(thread); + + return true; +} + +void +of_thread_set_name(const char *name) +{ +} ADDED src/platform/windows/tlskey.m Index: src/platform/windows/tlskey.m ================================================================== --- src/platform/windows/tlskey.m +++ src/platform/windows/tlskey.m @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, + * 2018, 2019, 2020 + * Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "tlskey.h" + +bool +of_tlskey_new(of_tlskey_t *key) +{ + return ((*key = TlsAlloc()) != TLS_OUT_OF_INDEXES); +} + +bool +of_tlskey_free(of_tlskey_t key) +{ + return TlsFree(key); +} Index: src/runtime/Makefile ================================================================== --- src/runtime/Makefile +++ src/runtime/Makefile @@ -31,11 +31,12 @@ static-instances.m \ synchronized.m \ ${USE_SRCS_THREADS} SRCS_THREADS = threading.m \ ../mutex.m \ - ../once.m + ../once.m \ + ../tlskey.m INCLUDES = ObjFWRT.h includesubdir = ObjFWRT OBJS_EXTRA = ${LOOKUP_ASM_LOOKUP_ASM_A} LIB_OBJS_EXTRA = ${LOOKUP_ASM_LOOKUP_ASM_LIB_A} Index: src/runtime/amiga-glue.m ================================================================== --- src/runtime/amiga-glue.m +++ src/runtime/amiga-glue.m @@ -747,5 +747,55 @@ { M68K_ARG(id, object, a0) return _objc_rootAutorelease(object); } + +struct objc_hashtable *__saveds +glue_objc_hashtable_new PPC_PARAMS(objc_hashtable_hash_func hash, + objc_hashtable_equal_func equal, uint32_t size) +{ + M68K_ARG(objc_hashtable_hash_func, hash, a0) + M68K_ARG(objc_hashtable_equal_func, equal, a1) + M68K_ARG(uint32_t, size, d0) + + return objc_hashtable_new(hash, equal, size); +} + +void __saveds +glue_objc_hashtable_set PPC_PARAMS(struct objc_hashtable *table, + const void *key, const void *object) +{ + M68K_ARG(struct objc_hashtable *, table, a0) + M68K_ARG(const void *, key, a1) + M68K_ARG(const void *, object, a2) + + objc_hashtable_set(table, key, object); +} + +void *__saveds +glue_objc_hashtable_get PPC_PARAMS(struct objc_hashtable *table, + const void *key) +{ + M68K_ARG(struct objc_hashtable *, table, a0) + M68K_ARG(const void *, key, a1) + + return objc_hashtable_get(table, key); +} + +void __saveds +glue_objc_hashtable_delete PPC_PARAMS(struct objc_hashtable *table, + const void *key) +{ + M68K_ARG(struct objc_hashtable *, table, a0) + M68K_ARG(const void *, key, a1) + + objc_hashtable_delete(table, key); +} + +void __saveds +glue_objc_hashtable_free PPC_PARAMS(struct objc_hashtable *table) +{ + M68K_ARG(struct objc_hashtable *, table, a0) + + objc_hashtable_free(table); +} Index: src/runtime/amiga-library.m ================================================================== --- src/runtime/amiga-library.m +++ src/runtime/amiga-library.m @@ -140,10 +140,15 @@ extern char *glue_property_copyAttributeValue(void); extern void *glue_objc_destructInstance(void); extern void *glue_objc_autoreleasePoolPush(void); extern void glue_objc_autoreleasePoolPop(void); extern id glue__objc_rootAutorelease(void); +extern struct objc_hashtable *glue_objc_hashtable_new(void); +extern void glue_objc_hashtable_set(void); +extern void *glue_objc_hashtable_get(void); +extern void glue_objc_hashtable_delete(void); +extern void glue_objc_hashtable_free(void); #ifdef OF_MORPHOS const ULONG __abox__ = 1; #endif struct ExecBase *SysBase; @@ -661,10 +666,15 @@ (CONST_APTR)glue_property_copyAttributeValue, (CONST_APTR)glue_objc_destructInstance, (CONST_APTR)glue_objc_autoreleasePoolPush, (CONST_APTR)glue_objc_autoreleasePoolPop, (CONST_APTR)glue__objc_rootAutorelease, + (CONST_APTR)glue_objc_hashtable_new, + (CONST_APTR)glue_objc_hashtable_set, + (CONST_APTR)glue_objc_hashtable_get, + (CONST_APTR)glue_objc_hashtable_delete, + (CONST_APTR)glue_objc_hashtable_free, (CONST_APTR)-1, #ifdef OF_MORPHOS (CONST_APTR)FUNCARRAY_END #endif }; Index: src/runtime/amigaos3.sfd ================================================================== --- src/runtime/amigaos3.sfd +++ src/runtime/amigaos3.sfd @@ -1,14 +1,12 @@ ==base _ObjFWRTBase ==basetype struct Library * ==libname objfwrt68k.library ==bias 30 ==public -* Functions that are only for the linklib. +* The following function is only for the linklib. bool glue_objc_init(unsigned int version, struct objc_libc *libc, FILE *stdout, FILE *stderr)(d0,a0,a1,a2) -* These have a built-in declaration in the compiler that does not use the -* registers and thus always need glue. void glue___objc_exec_class(struct objc_module *_Nonnull module)(a0) IMP _Nonnull glue_objc_msg_lookup(id _Nullable object, SEL _Nonnull selector)(a0,a1) IMP _Nonnull glue_objc_msg_lookup_stret(id _Nullable object, SEL _Nonnull selector)(a0,a1) IMP _Nonnull glue_objc_msg_lookup_super(struct objc_super *_Nonnull super, SEL _Nonnull selector)(a0,a1) IMP _Nonnull glue_objc_msg_lookup_super_stret(struct objc_super *_Nonnull super, SEL _Nonnull selector)(a0,a1) @@ -83,6 +81,12 @@ char *_Nullable glue_property_copyAttributeValue(objc_property_t _Nonnull property, const char *_Nonnull name)(a0,a1) void *_Nullable glue_objc_destructInstance(id _Nullable object)(a0) void *_Null_unspecified glue_objc_autoreleasePoolPush(void)() void glue_objc_autoreleasePoolPop(void *_Null_unspecified pool)(a0) id _Nullable glue__objc_rootAutorelease(id _Nullable object)(a0) +* The following functions are private! Don't use! +struct objc_hashtable *_Nonnull glue_objc_hashtable_new(objc_hashtable_hash_func hash, objc_hashtable_equal_func equal, uint32_t size)(a0,a1,d0) +void glue_objc_hashtable_set(struct objc_hashtable *_Nonnull table, const void *_Nonnull key, const void *_Nonnull object)(a0,a1,a2) +void *_Nullable glue_objc_hashtable_get(struct objc_hashtable *_Nonnull table, const void *_Nonnull key)(a0,a1) +void glue_objc_hashtable_delete(struct objc_hashtable *_Nonnull table, const void *_Nonnull key)(a0,a1) +void glue_objc_hashtable_free(struct objc_hashtable *_Nonnull table)(a0) ==end Index: src/runtime/autorelease.m ================================================================== --- src/runtime/autorelease.m +++ src/runtime/autorelease.m @@ -27,109 +27,107 @@ # import "tlskey.h" #endif #if defined(OF_HAVE_COMPILER_TLS) static thread_local id *objects = NULL; -static thread_local id *top = NULL; -static thread_local size_t size = 0; +static thread_local uintptr_t count = 0; +static thread_local uintptr_t size = 0; #elif defined(OF_HAVE_THREADS) -static of_tlskey_t objectsKey, topKey, sizeKey; +static of_tlskey_t objectsKey, countKey, sizeKey; #else static id *objects = NULL; -static id *top = NULL; -static size_t size = 0; +static uintptr_t count = 0; +static uintptr_t size = 0; #endif #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) OF_CONSTRUCTOR() { OF_ENSURE(of_tlskey_new(&objectsKey)); - OF_ENSURE(of_tlskey_new(&topKey)); + OF_ENSURE(of_tlskey_new(&countKey)); OF_ENSURE(of_tlskey_new(&sizeKey)); } #endif void * objc_autoreleasePoolPush() { #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - id *top = of_tlskey_get(topKey); - id *objects = of_tlskey_get(objectsKey); + uintptr_t count = (uintptr_t)of_tlskey_get(countKey); #endif - ptrdiff_t offset = top - objects; - - return (void *)offset; + return (void *)count; } void -objc_autoreleasePoolPop(void *offset) -{ -#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - id *top = of_tlskey_get(topKey); - id *objects = of_tlskey_get(objectsKey); -#endif - id *pool = objects + (ptrdiff_t)offset; - id *iter; - - for (iter = pool; iter < top; iter++) - [*iter release]; - - top = pool; - - if (top == objects) { - free(objects); - - objects = NULL; - top = NULL; - } - -#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - OF_ENSURE(of_tlskey_set(topKey, top)); - OF_ENSURE(of_tlskey_set(objectsKey, objects)); +objc_autoreleasePoolPop(void *pool) +{ +#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) + id *objects = of_tlskey_get(objectsKey); + uintptr_t count = (uintptr_t)of_tlskey_get(countKey); +#endif + uintptr_t idx = (uintptr_t)pool; + bool freeMem = false; + + if (idx == (uintptr_t)-1) { + idx++; + freeMem = true; + } + + for (uintptr_t i = idx; i < count; i++) { + [objects[i] release]; + +#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) + objects = of_tlskey_get(objectsKey); + count = (uintptr_t)of_tlskey_get(countKey); +#endif + } + + count = idx; + + if (freeMem) { + free(objects); + objects = NULL; +#if defined(OF_HAVE_COMPILER_TLS) || !defined(OF_HAVE_THREADS) + size = 0; +#else + OF_ENSURE(of_tlskey_set(objectsKey, objects)); + OF_ENSURE(of_tlskey_set(sizeKey, (void *)0)); +#endif + } + +#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) + OF_ENSURE(of_tlskey_set(countKey, (void *)count)); #endif } id _objc_rootAutorelease(id object) { #if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - id *top = of_tlskey_get(topKey); - id *objects = of_tlskey_get(objectsKey); - size_t size = (size_t)(uintptr_t)of_tlskey_get(sizeKey); -#endif - - if (objects == NULL) { - size = 16 * sizeof(id); - - OF_ENSURE((objects = malloc(size)) != NULL); - - top = objects; - -#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - OF_ENSURE(of_tlskey_set(objectsKey, objects)); - OF_ENSURE(of_tlskey_set(sizeKey, (void *)(uintptr_t)size)); -#endif - } - - if ((uintptr_t)top >= (uintptr_t)objects + size) { - ptrdiff_t diff = top - objects; - - size *= 2; - OF_ENSURE((objects = realloc(objects, size)) != NULL); - -#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - OF_ENSURE(of_tlskey_set(objectsKey, objects)); - OF_ENSURE(of_tlskey_set(sizeKey, (void *)(uintptr_t)size)); -#endif - - top = objects + diff; - } - - *top = object; - top++; - -#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) - OF_ENSURE(of_tlskey_set(topKey, top)); + id *objects = of_tlskey_get(objectsKey); + uintptr_t count = (uintptr_t)of_tlskey_get(countKey); + uintptr_t size = (uintptr_t)of_tlskey_get(sizeKey); +#endif + + if (count >= size) { + if (size == 0) + size = 16; + else + size *= 2; + + OF_ENSURE((objects = + realloc(objects, size * sizeof(id))) != NULL); + +#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) + OF_ENSURE(of_tlskey_set(objectsKey, objects)); + OF_ENSURE(of_tlskey_set(sizeKey, (void *)size)); +#endif + } + + objects[count++] = object; + +#if !defined(OF_HAVE_COMPILER_TLS) && defined(OF_HAVE_THREADS) + OF_ENSURE(of_tlskey_set(countKey, (void *)count)); #endif return object; } Index: src/runtime/linklib/linklib.m ================================================================== --- src/runtime/linklib/linklib.m +++ src/runtime/linklib/linklib.m @@ -673,5 +673,37 @@ id _objc_rootAutorelease(id object) { return glue__objc_rootAutorelease(object); } + +struct objc_hashtable * +objc_hashtable_new(objc_hashtable_hash_func hash, + objc_hashtable_equal_func equal, uint32_t size) +{ + return glue_objc_hashtable_new(hash, equal, size); +} + +void +objc_hashtable_set(struct objc_hashtable *table, const void *key, + const void *object) +{ + glue_objc_hashtable_set(table, key, object); +} + +void * +objc_hashtable_get(struct objc_hashtable *table, const void *key) +{ + return glue_objc_hashtable_get(table, key); +} + +void +objc_hashtable_delete(struct objc_hashtable *table, const void *key) +{ + glue_objc_hashtable_delete(table, key); +} + +void +objc_hashtable_free(struct objc_hashtable *table) +{ + glue_objc_hashtable_free(table); +} Index: src/runtime/morphos-clib.h ================================================================== --- src/runtime/morphos-clib.h +++ src/runtime/morphos-clib.h @@ -1,8 +1,7 @@ -/* Functions that are only for the linklib. */ +/* The following function is only for the linklib. */ bool glue_objc_init(unsigned int, struct objc_libc *, FILE *, FILE *); -/* All other functions. */ void glue___objc_exec_class(struct objc_module *); IMP glue_objc_msg_lookup(id, SEL); IMP glue_objc_msg_lookup_stret(id, SEL); IMP glue_objc_msg_lookup_super(struct objc_super *, SEL); IMP glue_objc_msg_lookup_super_stret(struct objc_super *, SEL); @@ -50,11 +49,11 @@ bool glue_class_respondsToSelector(Class, SEL); bool glue_class_conformsToProtocol(Class, Protocol *); IMP glue_class_getMethodImplementation(Class, SEL); IMP glue_class_getMethodImplementation_stret(Class, SEL); Method glue_class_getInstanceMethod(Class, SEL); -bool glue_class_addMethod(Class class_, SEL selector, IMP, const char *); +bool glue_class_addMethod(Class, SEL, IMP, const char *); IMP glue_class_replaceMethod(Class, SEL, IMP, const char *); Class glue_object_getClass(id); Class glue_object_setClass(id, Class); const char *glue_object_getClassName(id); const char *glue_protocol_getName(Protocol *); @@ -61,21 +60,27 @@ bool glue_protocol_isEqual(Protocol *, Protocol *); bool glue_protocol_conformsToProtocol(Protocol *, Protocol *); objc_uncaught_exception_handler_t glue_objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler_t); void glue_objc_setForwardHandler(IMP, IMP); void glue_objc_setEnumerationMutationHandler(objc_enumeration_mutation_handler_t); -id _Nullable glue_objc_constructInstance(Class class_, void *bytes); +id glue_objc_constructInstance(Class, void *); void glue_objc_exit(void); -Ivar *glue_class_copyIvarList(Class class_, unsigned int *outCount); -const char *glue_ivar_getName(Ivar ivar); -const char *glue_ivar_getTypeEncoding(Ivar ivar); -ptrdiff_t glue_ivar_getOffset(Ivar ivar); -Method *glue_class_copyMethodList(Class class_, unsigned int *outCount); -SEL glue_method_getName(Method method); -const char *glue_method_getTypeEncoding(Method method); -objc_property_t *glue_class_copyPropertyList(Class class_, unsigned int *outCount); -const char *glue_property_getName(objc_property_t property); -char *glue_property_copyAttributeValue(objc_property_t property, const char *name); -void *glue_objc_destructInstance(id object); +Ivar *glue_class_copyIvarList(Class, unsigned int *); +const char *glue_ivar_getName(Ivar); +const char *glue_ivar_getTypeEncoding(Ivar); +ptrdiff_t glue_ivar_getOffset(Ivar); +Method *glue_class_copyMethodList(Class, unsigned int *); +SEL glue_method_getName(Method); +const char *glue_method_getTypeEncoding(Method); +objc_property_t *glue_class_copyPropertyList(Class, unsigned int *); +const char *glue_property_getName(objc_property_t); +char *glue_property_copyAttributeValue(objc_property_t, const char *); +void *glue_objc_destructInstance(id); void *glue_objc_autoreleasePoolPush(void); -void glue_objc_autoreleasePoolPop(void *pool); -id glue__objc_rootAutorelease(id object); +void glue_objc_autoreleasePoolPop(void *); +id glue__objc_rootAutorelease(id); +/* The following functions are private! Don't use! */ +struct objc_hashtable *glue_objc_hashtable_new(objc_hashtable_hash_func hash, objc_hashtable_equal_func equal, uint32_t size); +void glue_objc_hashtable_set(struct objc_hashtable *table, const void *key, const void *object); +void *glue_objc_hashtable_get(struct objc_hashtable *table, const void *key); +void glue_objc_hashtable_delete(struct objc_hashtable *table, const void *key); +void glue_objc_hashtable_free(struct objc_hashtable *table); Index: src/runtime/morphos.fd ================================================================== --- src/runtime/morphos.fd +++ src/runtime/morphos.fd @@ -1,9 +1,9 @@ ##base _ObjFWRTBase ##bias 30 ##public -* Functions that are only for the linklib. +* The following function is only for the linklib. glue_objc_init(version,libc,stdout,stderr)(sysv,r12base) glue___objc_exec_class(module)(sysv,r12base) glue_objc_msg_lookup(object,selector)(sysv,r12base) glue_objc_msg_lookup_stret(object,selector)(sysv,r12base) glue_objc_msg_lookup_super(super,selector)(sysv,r12base) @@ -76,9 +76,15 @@ glue_method_getTypeEncoding(method)(sysv,r12base) glue_class_copyPropertyList(class_,outCount)(sysv,r12base) glue_property_getName(property)(sysv,r12base) glue_property_copyAttributeValue(property,name)(sysv,r12base) glue_objc_destructInstance(object)(sysv,r12base) -objc_autoreleasePoolPush()(sysv,r12base) -objc_autoreleasePoolPop(pool)(sysv,r12base) -_objc_rootAutorelease(object)(sysv,r12base) +glue_objc_autoreleasePoolPush()(sysv,r12base) +glue_objc_autoreleasePoolPop(pool)(sysv,r12base) +glue__objc_rootAutorelease(object)(sysv,r12base) +* The following functions are private! Don't use! +glue_objc_hashtable_new(hash,equal,size)(sysv,r12base) +glue_objc_hashtable_set(table,key,object)(sysv,r12base) +glue_objc_hashtable_get(table,key)(sysv,r12base) +glue_objc_hashtable_delete(table,key)(sysv,r12base) +glue_objc_hashtable_free(table)(sysv,r12base) ##end Index: src/runtime/private.h ================================================================== --- src/runtime/private.h +++ src/runtime/private.h @@ -27,10 +27,14 @@ # ifndef _Nullable # define _Nullable # endif #endif +typedef uint32_t (*_Nonnull objc_hashtable_hash_func)(const void *_Nonnull key); +typedef bool (*_Nonnull objc_hashtable_equal_func)(const void *_Nonnull key1, + const void *_Nonnull key2); + struct objc_class { Class _Nonnull isa; Class _Nullable superclass; const char *_Nonnull name; unsigned long version; @@ -187,13 +191,12 @@ const void *_Nonnull key, *_Nonnull object; uint32_t hash; }; struct objc_hashtable { - uint32_t (*_Nonnull hash)(const void *_Nonnull key); - bool (*_Nonnull equal)(const void *_Nonnull key1, - const void *_Nonnull key2); + objc_hashtable_hash_func hash; + objc_hashtable_equal_func equal; uint32_t count, size; struct objc_hashtable_bucket *_Nonnull *_Nullable data; }; struct objc_sparsearray { @@ -276,12 +279,11 @@ extern void objc_unregister_class(Class _Nonnull); extern void objc_unregister_all_classes(void); extern uint32_t objc_hash_string(const void *_Nonnull); extern bool objc_equal_string(const void *_Nonnull, const void *_Nonnull); extern struct objc_hashtable *_Nonnull objc_hashtable_new( - uint32_t (*_Nonnull)(const void *_Nonnull), - bool (*_Nonnull)(const void *_Nonnull, const void *_Nonnull), uint32_t); + objc_hashtable_hash_func, objc_hashtable_equal_func, uint32_t); extern struct objc_hashtable_bucket objc_deleted_bucket; extern void objc_hashtable_set(struct objc_hashtable *_Nonnull, const void *_Nonnull, const void *_Nonnull); extern void *_Nullable objc_hashtable_get(struct objc_hashtable *_Nonnull, const void *_Nonnull); Index: src/socket.m ================================================================== --- src/socket.m +++ src/socket.m @@ -56,11 +56,11 @@ #endif #if defined(OF_HAVE_THREADS) && !defined(OF_AMIGAOS) static of_mutex_t mutex; #endif -#ifndef OF_AMIGAOS +#if !defined(OF_AMIGAOS) || !defined(OF_HAVE_THREADS) static bool initSuccessful = false; #else # ifdef OF_HAVE_THREADS of_tlskey_t of_socket_base_key; # ifdef OF_AMIGAOS4 Index: src/socket_helpers.h ================================================================== --- src/socket_helpers.h +++ src/socket_helpers.h @@ -47,11 +47,15 @@ #ifndef SOCK_CLOEXEC # define SOCK_CLOEXEC 0 #endif #if defined(OF_AMIGAOS) -# include +# ifdef OF_MORPHOS +# include +# else +# include +# endif # include # define closesocket(sock) CloseSocket(sock) # define ioctlsocket(fd, req, arg) IoctlSocket(fd, req, arg) # define hstrerror(err) "unknown (no hstrerror)" # define SOCKET_ERROR -1 Index: src/thread.m ================================================================== --- src/thread.m +++ src/thread.m @@ -15,14 +15,14 @@ * file. */ #include "config.h" -#import "thread.h" +#include "platform.h" #if defined(OF_HAVE_PTHREADS) -# include "thread_pthread.m" +# include "platform/posix/thread.m" #elif defined(OF_WINDOWS) -# include "thread_winapi.m" +# include "platform/windows/thread.m" #elif defined(OF_AMIGAOS) -# include "thread_amiga.m" +# include "platform/amiga/thread.m" #endif DELETED src/thread_amiga.m Index: src/thread_amiga.m ================================================================== --- src/thread_amiga.m +++ src/thread_amiga.m @@ -1,216 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include -#include - -#include -#include -#include - -#import "OFData.h" - -#import "tlskey.h" - -extern void of_tlskey_thread_exited(void); -static of_tlskey_t threadKey; - -OF_CONSTRUCTOR() -{ - OF_ENSURE(of_tlskey_new(&threadKey)); -} - -static void -functionWrapper(void) -{ - bool detached = false; - of_thread_t thread = - (of_thread_t)((struct Process *)FindTask(NULL))->pr_ExitData; - OF_ENSURE(of_tlskey_set(threadKey, thread)); - - thread->function(thread->object); - - ObtainSemaphore(&thread->semaphore); - @try { - thread->done = true; - - of_tlskey_thread_exited(); - - if (thread->detached) - detached = true; - else if (thread->joinTask != NULL) - Signal(thread->joinTask, (1ul << thread->joinSigBit)); - } @finally { - ReleaseSemaphore(&thread->semaphore); - } - - if (detached) - free(thread); -} - -bool -of_thread_attr_init(of_thread_attr_t *attr) -{ - attr->priority = 0; - attr->stackSize = 0; - - return true; -} - -bool -of_thread_new(of_thread_t *thread, const char *name, void (*function)(id), - id object, const of_thread_attr_t *attr) -{ - OFMutableData *tags = nil; - - if ((*thread = calloc(1, sizeof(**thread))) == NULL) { - errno = ENOMEM; - return false; - } - - @try { - (*thread)->function = function; - (*thread)->object = object; - InitSemaphore(&(*thread)->semaphore); - - tags = [[OFMutableData alloc] - initWithItemSize: sizeof(struct TagItem) - capacity: 12]; -#define ADD_TAG(tag, data) \ - { \ - struct TagItem t = { \ - .ti_Tag = tag, \ - .ti_Data = data \ - }; \ - [tags addItem: &t]; \ - } - ADD_TAG(NP_Entry, (ULONG)functionWrapper) - ADD_TAG(NP_ExitData, (ULONG)*thread) -#ifdef OF_AMIGAOS4 - ADD_TAG(NP_Child, TRUE) -#endif -#ifdef OF_MORPHOS - ADD_TAG(NP_CodeType, CODETYPE_PPC); -#endif - if (name != NULL) - ADD_TAG(NP_Name, (ULONG)name); - - ADD_TAG(NP_Input, ((struct Process *)FindTask(NULL))->pr_CIS) - ADD_TAG(NP_Output, ((struct Process *)FindTask(NULL))->pr_COS) - ADD_TAG(NP_Error, ((struct Process *)FindTask(NULL))->pr_CES) - ADD_TAG(NP_CloseInput, FALSE) - ADD_TAG(NP_CloseOutput, FALSE) - ADD_TAG(NP_CloseError, FALSE) - - if (attr != NULL && attr->priority != 0) { - if (attr->priority < 1 || attr->priority > 1) { - errno = EINVAL; - return false; - } - - /* - * -1 should be -128 (lowest possible priority) while - * +1 should be +127 (highest possible priority). - */ - ADD_TAG(NP_Priority, (attr->priority > 0 - ? attr->priority * 127 : attr->priority * 128)) - } - - if (attr != NULL && attr->stackSize != 0) - ADD_TAG(NP_StackSize, attr->stackSize) - else - ADD_TAG(NP_StackSize, - ((struct Process *)FindTask(NULL))->pr_StackSize) - - ADD_TAG(TAG_DONE, 0) -#undef ADD_TAG - - (*thread)->task = (struct Task *)CreateNewProc(tags.items); - if ((*thread)->task == NULL) { - free(*thread); - errno = EAGAIN; - return false; - } - } @catch (id e) { - free(*thread); - @throw e; - } @finally { - [tags release]; - } - - return true; -} - -of_thread_t -of_thread_current(void) -{ - return of_tlskey_get(threadKey); -} - -bool -of_thread_join(of_thread_t thread) -{ - ObtainSemaphore(&thread->semaphore); - @try { - if (thread->done) { - free(thread); - return true; - } - - if (thread->detached || thread->joinTask != NULL) { - errno = EINVAL; - return false; - } - - if ((thread->joinSigBit = AllocSignal(-1)) == -1) { - errno = EAGAIN; - return false; - } - - thread->joinTask = FindTask(NULL); - } @finally { - ReleaseSemaphore(&thread->semaphore); - } - - Wait(1ul << thread->joinSigBit); - FreeSignal(thread->joinSigBit); - - assert(thread->done); - free(thread); - - return true; -} - -bool -of_thread_detach(of_thread_t thread) -{ - ObtainSemaphore(&thread->semaphore); - - if (thread->done) - free(thread); - else - thread->detached = true; - - ReleaseSemaphore(&thread->semaphore); - - return true; -} - -void -of_thread_set_name(const char *name) -{ -} DELETED src/thread_pthread.m Index: src/thread_pthread.m ================================================================== --- src/thread_pthread.m +++ src/thread_pthread.m @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include - -#ifdef HAVE_PTHREAD_NP_H -# include -#endif - -#ifdef OF_HAIKU -# include -#endif - -#import "macros.h" - -static int minPrio = 0, maxPrio = 0, normalPrio = 0; - -struct thread_ctx { - void (*function)(id object); - id object; - const char *name; -}; - -/* - * This is done here to make sure this is done as early as possible in the main - * thread. - */ -OF_CONSTRUCTOR() -{ - pthread_attr_t pattr; - - if (pthread_attr_init(&pattr) == 0) { -#ifdef HAVE_PTHREAD_ATTR_GETSCHEDPOLICY - int policy; -#endif - struct sched_param param; - -#ifdef HAVE_PTHREAD_ATTR_GETSCHEDPOLICY - if (pthread_attr_getschedpolicy(&pattr, &policy) == 0) { - minPrio = sched_get_priority_min(policy); - maxPrio = sched_get_priority_max(policy); - - if (minPrio == -1 || maxPrio == -1) - minPrio = maxPrio = 0; - } - - if (pthread_attr_getschedparam(&pattr, ¶m) != 0) - normalPrio = param.sched_priority; - else - minPrio = maxPrio = 0; - - pthread_attr_destroy(&pattr); -#endif - } -} - -static void * -functionWrapper(void *data) -{ - struct thread_ctx *ctx = data; - - if (ctx->name != NULL) - of_thread_set_name(ctx->name); - - pthread_cleanup_push(free, data); - - ctx->function(ctx->object); - - pthread_cleanup_pop(1); - return NULL; -} - -bool -of_thread_attr_init(of_thread_attr_t *attr) -{ - pthread_attr_t pattr; - - if (pthread_attr_init(&pattr) != 0) - return false; - - @try { - attr->priority = 0; - - if (pthread_attr_getstacksize(&pattr, &attr->stackSize) != 0) - return false; - } @finally { - pthread_attr_destroy(&pattr); - } - - return true; -} - -bool -of_thread_new(of_thread_t *thread, const char *name, void (*function)(id), - id object, const of_thread_attr_t *attr) -{ - bool ret; - pthread_attr_t pattr; - - if (pthread_attr_init(&pattr) != 0) - return false; - - @try { - struct thread_ctx *ctx; - - if (attr != NULL) { - struct sched_param param; - - if (attr->priority < -1 || attr->priority > 1) { - errno = EINVAL; - return false; - } - -#ifdef HAVE_PTHREAD_ATTR_SETINHERITSCHED - if (pthread_attr_setinheritsched(&pattr, - PTHREAD_EXPLICIT_SCHED) != 0) - return false; -#endif - - if (attr->priority < 0) { - param.sched_priority = minPrio + - (1.0f + attr->priority) * - (normalPrio - minPrio); - } else - param.sched_priority = normalPrio + - attr->priority * (maxPrio - normalPrio); - - if (pthread_attr_setschedparam(&pattr, ¶m) != 0) - return false; - - if (attr->stackSize > 0) { - if (pthread_attr_setstacksize(&pattr, - attr->stackSize) != 0) - return false; - } - } - - if ((ctx = malloc(sizeof(*ctx))) == NULL) { - errno = ENOMEM; - return false; - } - - ctx->function = function; - ctx->object = object; - ctx->name = name; - - ret = (pthread_create(thread, &pattr, - functionWrapper, ctx) == 0); - } @finally { - pthread_attr_destroy(&pattr); - } - - return ret; -} - -bool -of_thread_join(of_thread_t thread) -{ - void *ret; - - return (pthread_join(thread, &ret) == 0); -} - -bool -of_thread_detach(of_thread_t thread) -{ - return (pthread_detach(thread) == 0); -} - -void -of_thread_set_name(const char *name) -{ -#if defined(OF_HAIKU) - rename_thread(find_thread(NULL), name); -#elif defined(HAVE_PTHREAD_SET_NAME_NP) - pthread_set_name_np(pthread_self(), name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) -# if defined(OF_MACOS) || defined(OF_IOS) - pthread_setname_np(name); -# elif defined(__GLIBC__) - char buffer[16]; - - strncpy(buffer, name, 15); - buffer[15] = 0; - - pthread_setname_np(pthread_self(), buffer); -# endif -#endif -} DELETED src/thread_winapi.m Index: src/thread_winapi.m ================================================================== --- src/thread_winapi.m +++ src/thread_winapi.m @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, - * 2018, 2019, 2020 - * Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include - -#import "macros.h" - -bool -of_thread_attr_init(of_thread_attr_t *attr) -{ - attr->priority = 0; - attr->stackSize = 0; - - return true; -} - -bool -of_thread_new(of_thread_t *thread, const char *name, void (*function)(id), - id object, const of_thread_attr_t *attr) -{ - *thread = CreateThread(NULL, (attr != NULL ? attr->stackSize : 0), - (LPTHREAD_START_ROUTINE)function, (void *)object, 0, NULL); - - if (thread == NULL) { - switch (GetLastError()) { - case ERROR_NOT_ENOUGH_MEMORY: - errno = ENOMEM; - return false; - case ERROR_ACCESS_DENIED: - errno = EACCES; - return false; - default: - OF_ENSURE(0); - } - } - - if (attr != NULL && attr->priority != 0) { - DWORD priority; - - if (attr->priority < -1 || attr->priority > 1) { - errno = EINVAL; - return false; - } - - if (attr->priority < 0) - priority = THREAD_PRIORITY_LOWEST + - (1.0 + attr->priority) * - (THREAD_PRIORITY_NORMAL - THREAD_PRIORITY_LOWEST); - else - priority = THREAD_PRIORITY_NORMAL + - attr->priority * - (THREAD_PRIORITY_HIGHEST - THREAD_PRIORITY_NORMAL); - - OF_ENSURE(!SetThreadPriority(*thread, priority)); - } - - return true; -} - -bool -of_thread_join(of_thread_t thread) -{ - switch (WaitForSingleObject(thread, INFINITE)) { - case WAIT_OBJECT_0: - CloseHandle(thread); - return true; - case WAIT_FAILED: - switch (GetLastError()) { - case ERROR_INVALID_HANDLE: - errno = EINVAL; - return false; - default: - OF_ENSURE(0); - } - default: - OF_ENSURE(0); - } -} - -bool -of_thread_detach(of_thread_t thread) -{ - CloseHandle(thread); - - return true; -} - -void -of_thread_set_name(const char *name) -{ -} Index: src/tlskey.h ================================================================== --- src/tlskey.h +++ src/tlskey.h @@ -31,15 +31,13 @@ typedef pthread_key_t of_tlskey_t; #elif defined(OF_WINDOWS) # include typedef DWORD of_tlskey_t; #elif defined(OF_AMIGAOS) -# import "OFList.h" -@class OFMapTable; -typedef struct { - OFMapTable *mapTable; - of_list_object_t *listObject; +typedef struct of_tlskey { + struct objc_hashtable *table; + struct of_tlskey *next, *previous; } *of_tlskey_t; #endif #ifdef __cplusplus extern "C" { Index: src/tlskey.m ================================================================== --- src/tlskey.m +++ src/tlskey.m @@ -15,135 +15,14 @@ * file. */ #include "config.h" -#import "tlskey.h" - -#ifdef OF_AMIGAOS -# include -# include - -# import "OFMapTable.h" -# import "OFList.h" - -static const of_map_table_functions_t functions = { NULL }; -static OFList *allKeys = nil; -static struct SignalSemaphore semaphore; - -OF_CONSTRUCTOR() -{ - InitSemaphore(&semaphore); -} -#endif - -bool -of_tlskey_new(of_tlskey_t *key) -{ -#if defined(OF_HAVE_PTHREADS) - return (pthread_key_create(key, NULL) == 0); -#elif defined(OF_WINDOWS) - return ((*key = TlsAlloc()) != TLS_OUT_OF_INDEXES); -#elif defined(OF_AMIGAOS) - if ((*key = calloc(1, sizeof(**key))) == NULL) - return false; - - /* - * We create the map table lazily, as some TLS are created in - * constructors, at which time OFMapTable is not available yet. - */ - - return true; -#endif -} - -bool -of_tlskey_free(of_tlskey_t key) -{ -#if defined(OF_HAVE_PTHREADS) - return (pthread_key_delete(key) == 0); -#elif defined(OF_WINDOWS) - return TlsFree(key); -#elif defined(OF_AMIGAOS) - ObtainSemaphore(&semaphore); - @try { - [allKeys removeListObject: key->listObject]; - [key->mapTable release]; - free(key); - } @finally { - ReleaseSemaphore(&semaphore); - } - - return true; -#endif -} - -#ifdef OF_AMIGAOS -static void -unsafeCreateMapTable(of_tlskey_t key) -{ - key->mapTable = [[OFMapTable alloc] initWithKeyFunctions: functions - objectFunctions: functions]; - - if (allKeys == nil) - allKeys = [[OFList alloc] init]; - - key->listObject = [allKeys appendObject: key->mapTable]; -} - -void * -of_tlskey_get(of_tlskey_t key) -{ - void *ret; - - ObtainSemaphore(&semaphore); - @try { - if (key->mapTable == NULL) - unsafeCreateMapTable(key); - - ret = [key->mapTable objectForKey: FindTask(NULL)]; - } @finally { - ReleaseSemaphore(&semaphore); - } - - return ret; -} - -bool -of_tlskey_set(of_tlskey_t key, void *ptr) -{ - ObtainSemaphore(&semaphore); - @try { - struct Task *task = FindTask(NULL); - - if (key->mapTable == NULL) - unsafeCreateMapTable(key); - - if (ptr == NULL) - [key->mapTable removeObjectForKey: task]; - else - [key->mapTable setObject: ptr - forKey: task]; - } @catch (id e) { - return false; - } @finally { - ReleaseSemaphore(&semaphore); - } - - return true; -} - -void -of_tlskey_thread_exited(void) -{ - ObtainSemaphore(&semaphore); - @try { - struct Task *task = FindTask(NULL); - - for (of_list_object_t *iter = allKeys.firstListObject; - iter != NULL; iter = iter->next) - [iter->object removeObjectForKey: task]; - } @finally { - ReleaseSemaphore(&semaphore); - } -} +#include "platform.h" + +#if defined(OF_HAVE_PTHREADS) +# include "platform/posix/tlskey.m" +#elif defined(OF_WINDOWS) +# include "platform/windows/tlskey.m" +#elif defined(OF_AMIGAOS) +# include "platform/amiga/tlskey.m" #endif Index: tests/OFURLTests.m ================================================================== --- tests/OFURLTests.m +++ tests/OFURLTests.m @@ -25,19 +25,21 @@ @implementation TestsAppDelegate (OFURLTests) - (void)URLTests { void *pool = objc_autoreleasePoolPush(); - OFURL *u1, *u2, *u3, *u4, *u5; + OFURL *u1, *u2, *u3, *u4, *u5, *u6, *u7; OFMutableURL *mu; TEST(@"+[URLWithString:]", R(u1 = [OFURL URLWithString: url_str]) && R(u2 = [OFURL URLWithString: @"http://foo:80"]) && R(u3 = [OFURL URLWithString: @"http://bar/"]) && R(u4 = [OFURL URLWithString: @"file:///etc/passwd"]) && - R(u5 = [OFURL URLWithString: @"http://foo/bar/qux/foo%2fbar"])) + R(u5 = [OFURL URLWithString: @"http://foo/bar/qux/foo%2fbar"]) && + R(u6 = [OFURL URLWithString: @"https://[12:34::56:abcd]/"]) && + R(u7 = [OFURL URLWithString: @"https://[12:34::56:abcd]:234/"])) EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #1", OFInvalidFormatException, [OFURL URLWithString: @"ht,tp://foo"]) @@ -54,10 +56,22 @@ [OFURL URLWithString: @"http://foo/foo?`"]) EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #5", OFInvalidFormatException, [OFURL URLWithString: @"http://foo/foo?foo#`"]) + + EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #6", + OFInvalidFormatException, + [OFURL URLWithString: @"https://[g]/"]) + + EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #7", + OFInvalidFormatException, + [OFURL URLWithString: @"https://[f]:/"]) + + EXPECT_EXCEPTION(@"+[URLWithString:] fails with invalid characters #8", + OFInvalidFormatException, + [OFURL URLWithString: @"https://[f]:f/"]) TEST(@"+[URLWithString:relativeToURL:]", [[[OFURL URLWithString: @"/foo" relativeToURL: u1] string] isEqual: @"ht%3atp://us%3Aer:p%40w@ho%3Ast:1234/foo"] && @@ -137,12 +151,16 @@ [u1.scheme isEqual: @"ht:tp"] && [u4.scheme isEqual: @"file"]) TEST(@"-[user]", [u1.user isEqual: @"us:er"] && u4.user == nil) TEST(@"-[password]", [u1.password isEqual: @"p@w"] && u4.password == nil) - TEST(@"-[host]", [u1.host isEqual: @"ho:st"] && [u4 port] == nil) - TEST(@"-[port]", [u1.port isEqual: [OFNumber numberWithUInt16: 1234]]) + TEST(@"-[host]", [u1.host isEqual: @"ho:st"] && + [u6.host isEqual: @"12:34::56:abcd"] && + [u7.host isEqual: @"12:34::56:abcd"]) + TEST(@"-[port]", [u1.port isEqual: [OFNumber numberWithUInt16: 1234]] && + [u4 port] == nil && + [u7.port isEqual: [OFNumber numberWithUInt16: 234]]) TEST(@"-[path]", [u1.path isEqual: @"/pa?th"] && [u4.path isEqual: @"/etc/passwd"]) TEST(@"-[pathComponents]", [u1.pathComponents isEqual: [OFArray arrayWithObjects: @"/", @"pa?th", nil]] && @@ -186,17 +204,31 @@ EXPECT_EXCEPTION( @"-[setURLEncodedScheme:] with invalid characters fails", OFInvalidFormatException, mu.URLEncodedScheme = @"~") TEST(@"-[setHost:]", - (mu.host = @"ho:st") && [mu.URLEncodedHost isEqual: @"ho%3Ast"]) + (mu.host = @"ho:st") && [mu.URLEncodedHost isEqual: @"ho%3Ast"] && + (mu.host = @"12:34:ab") && + [mu.URLEncodedHost isEqual: @"[12:34:ab]"] && + (mu.host = @"12:34:aB") && + [mu.URLEncodedHost isEqual: @"[12:34:aB]"] && + (mu.host = @"12:34:g") && + [mu.URLEncodedHost isEqual: @"12%3A34%3Ag"]) TEST(@"-[setURLEncodedHost:]", - (mu.URLEncodedHost = @"ho%3Ast") && [mu.host isEqual: @"ho:st"]) + (mu.URLEncodedHost = @"ho%3Ast") && [mu.host isEqual: @"ho:st"] && + (mu.URLEncodedHost = @"[12:34]") && [mu.host isEqual: @"12:34"] && + (mu.URLEncodedHost = @"[12::ab]") && [mu.host isEqual: @"12::ab"]) + + EXPECT_EXCEPTION(@"-[setURLEncodedHost:] with invalid characters fails" + " #1", OFInvalidFormatException, mu.URLEncodedHost = @"/") + + EXPECT_EXCEPTION(@"-[setURLEncodedHost:] with invalid characters fails" + " #2", OFInvalidFormatException, mu.URLEncodedHost = @"[12:34") - EXPECT_EXCEPTION(@"-[setURLEncodedHost:] with invalid characters fails", - OFInvalidFormatException, mu.URLEncodedHost = @"/") + EXPECT_EXCEPTION(@"-[setURLEncodedHost:] with invalid characters fails" + " #3", OFInvalidFormatException, mu.URLEncodedHost = @"[a::g]") TEST(@"-[setUser:]", (mu.user = @"us:er") && [mu.URLEncodedUser isEqual: @"us%3Aer"]) TEST(@"-[setURLEncodedUser:]", Index: utils/Makefile ================================================================== --- utils/Makefile +++ utils/Makefile @@ -1,11 +1,12 @@ include ../extra.mk SUBDIRS += ${OFARC} \ ${OFDNS} \ ${OFHASH} \ - ${OFHTTP} + ${OFHTTP} \ + completions include ../buildsys.mk DISTCLEAN = objfw-config ADDED utils/completions/Makefile Index: utils/completions/Makefile ================================================================== --- utils/completions/Makefile +++ utils/completions/Makefile @@ -0,0 +1,5 @@ +include ../../extra.mk + +SUBDIRS = ${FISH_COMPLETIONS} + +include ../../buildsys.mk ADDED utils/completions/fish/Makefile Index: utils/completions/fish/Makefile ================================================================== --- utils/completions/fish/Makefile +++ utils/completions/fish/Makefile @@ -0,0 +1,9 @@ +DATA = objfw-compile.fish \ + objfw-config.fish \ + ofarc.fish \ + ofhash.fish \ + ofhttp.fish + +include ../../../buildsys.mk + +PACKAGE_NAME = fish/vendor_completions.d ADDED utils/completions/fish/objfw-compile.fish Index: utils/completions/fish/objfw-compile.fish ================================================================== --- utils/completions/fish/objfw-compile.fish +++ utils/completions/fish/objfw-compile.fish @@ -0,0 +1,28 @@ +complete -c objfw-compile -s o -x -d 'Specify the output name (not file name!)' +complete -c objfw-compile -l arc -d 'Use automatic reference counting' +complete -c objfw-compile -l lib -x -d \ + 'Compile a library (with the specified version) instead of an application' +complete -c objfw-compile -l plugin \ + -d 'Compile a plugin instead of an application' +complete -c objfw-compile -l package -x -d 'Use the specified package' +complete -c objfw-compile -l builddir -r \ + -d 'Place built objects into the specified directory' +complete -c objfw-compile -s D -x -d 'Pass the specified define to the compiler' +complete -c objfw-compile -o framework -x \ + -d 'Pass the specified -framework argument to the linker (macOS / iOS only)' +# -f* cannot be represented. +complete -c objfw-compile -s F -x \ + -d 'Pass the specified -F flag to the linker (macOS / iOS only)' +# -g* cannot be represented. +complete -c objfw-compile -s I -x \ + -d 'Pass the specified -I flag to the compiler' +complete -c objfw-compile -s l -x -d 'Pass the specified -l flag to the linker' +complete -c objfw-compile -s L -x -d 'Pass the specified -L flag to the linker' +# -m* cannot be represented. +# -O* cannot be represented. +complete -c objfw-compile -o pthread \ + -d 'Pass -pthread to the compiler and linker' +# -std=* cannot be represented. +# -Wl,* cannot be represented. +# -W* cannot be represented. +complete -c objfw-compile -l help -d 'Show this help' ADDED utils/completions/fish/objfw-config.fish Index: utils/completions/fish/objfw-config.fish ================================================================== --- utils/completions/fish/objfw-config.fish +++ utils/completions/fish/objfw-config.fish @@ -0,0 +1,33 @@ +complete -c objfw-config -l all -d 'Outputs all flags + libs' +complete -c objfw-config -l arc -d 'Outputs the required OBJCFLAGS to use ARC' +complete -c objfw-config -l cflags -d 'Outputs the required CFLAGS' +complete -c objfw-config -l cppflags -d 'Outputs the required CPPFLAGS' +complete -c objfw-config -l cxxflags -d 'Outputs the required CXXFLAGS' +complete -c objfw-config -l framework-libs \ + -d 'Outputs the required LIBS, preferring frameworks' +complete -c objfw-config -l help -d 'Print help' +complete -c objfw-config -l ldflags -d 'Outputs the required LDFLAGS' +complete -c objfw-config -l libs -d 'Outputs the required LIBS' +complete -c objfw-config -l lib-cflags \ + -d 'Outputs CFLAGS for building a library' +complete -c objfw-config -l lib-ldflags \ + -d 'Outputs LDFLAGS for building a library' +complete -c objfw-config -l lib-prefix -d 'Outputs the prefix for libraries' +complete -c objfw-config -l lib-suffix -d 'Outputs the suffix for libraries' +complete -c objfw-config -l objc -d 'Outputs the OBJC used to compile ObjFW' +complete -c objfw-config -l objcflags -d 'Outputs the required OBJCFLAGS' +complete -c objfw-config -l package -x \ + -d 'Additionally outputs the flags for the specified package' +complete -c objfw-config -l packages-dir \ + -d 'Outputs the directory where flags for packages are stored' +complete -c objfw-config -l plugin-cflags \ + -d 'Outputs CFLAGS for building a plugin' +complete -c objfw-config -l plugin-ldflags \ + -d 'Outputs LDFLAGS for building a plugin' +complete -c objfw-config -l plugin-suffix -d 'Outputs the suffix for plugins' +complete -c objfw-config -l prog-suffix -d 'Outputs the suffix for binaries' +complete -c objfw-config -l reexport -d 'Outputs LDFLAGS to reexport ObjFW' +complete -c objfw-config -l rpath -d 'Outputs LDFLAGS for using rpath' +complete -c objfw-config -l static-libs \ + -d 'Outputs the required LIBS to link ObjFW statically' +complete -c objfw-config -l version -d 'Outputs the installed version' ADDED utils/completions/fish/ofarc.fish Index: utils/completions/fish/ofarc.fish ================================================================== --- utils/completions/fish/ofarc.fish +++ utils/completions/fish/ofarc.fish @@ -0,0 +1,14 @@ +complete -c ofarc -s a -l append -d 'Append to archive' +complete -c ofarc -s c -l create -d 'Create archive' +complete -c ofarc -s C -l directory -r -d 'Extract into the specified directory' +complete -c ofarc -s E -l encoding -x \ + -d 'The encoding used by the archive (only tar files)' +complete -c ofarc -s f -l force -d 'Force / overwrite files' +complete -c ofarc -s h -l help -d 'Show help' +complete -c ofarc -s l -l list -d 'List all files in the archive' +complete -c ofarc -s n -l no-clobber -d 'Never overwrite files' +complete -c ofarc -s p -l print -d 'Print one or more files from the archive' +complete -c ofarc -s q -l quiet -d 'Quiet mode (no output, except errors)' +complete -c ofarc -s t -l type -x -a 'gz lha tar tgz zip' -d 'Archive type' +complete -c ofarc -s v -l verbose -d 'Verbose output for file list' +complete -c ofarc -s x -l extract -d 'Extract files' ADDED utils/completions/fish/ofhash.fish Index: utils/completions/fish/ofhash.fish ================================================================== --- utils/completions/fish/ofhash.fish +++ utils/completions/fish/ofhash.fish @@ -0,0 +1,7 @@ +complete -c ofhash --long-option md5 +complete -c ofhash --long-option ripemd160 +complete -c ofhash --long-option sha1 +complete -c ofhash --long-option sha224 +complete -c ofhash --long-option sha256 +complete -c ofhash --long-option sha384 +complete -c ofhash --long-option sha256 ADDED utils/completions/fish/ofhttp.fish Index: utils/completions/fish/ofhttp.fish ================================================================== --- utils/completions/fish/ofhttp.fish +++ utils/completions/fish/ofhttp.fish @@ -0,0 +1,14 @@ +complete -c ofhttp -s b -l body -r -d 'Specify the file to send as body' +complete -c ofhttp -s c -l continue -d 'Continue download of existing file' +complete -c ofhttp -s f -l force -d 'Force / overwrite existing file' +complete -c ofhttp -s h -l help -d 'Show help' +complete -c ofhttp -s H -l header -x -d 'Add a header (e.g. X-Foo:Bar)' +complete -c ofhttp -s m -l method -x -d 'Set the method of the HTTP request' +complete -c ofhttp -s o -l output -r -d 'Specify output file name' +complete -c ofhttp -s O -l detect-filename \ + -d 'Do a HEAD request to detect the file name' +complete -c ofhttp -s P -l proxy -x -d 'Specify SOCKS5 proxy' +complete -c ofhttp -s q -l quiet -d 'Quiet mode (no output, except errors)' +complete -c ofhttp -s v -l verbose -d 'Verbose mode (print headers)' +complete -c ofhttp -l insecure \ + -d 'Ignore TLS errors and allow insecure redirects' Index: utils/objfw-compile ================================================================== --- utils/objfw-compile +++ utils/objfw-compile @@ -65,11 +65,11 @@ -m* Pass the specified -m flag to the compiler -O* Pass the specified -O flag to the compiler -pthread Pass -pthread to the compiler and linker -std=* Pass the specified -std= flag to the compiler -Wl,* Pass the specified -Wl, flag to the linker - -W* Pass the specified -W flag to the compiler" + -W* Pass the specified -W flag to the compiler --help Show this help __EOF__ } CPPFLAGS="$CPPFLAGS $($OBJFW_CONFIG $packages --cppflags)" Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -603,10 +603,12 @@ } if (!_quiet) [of_stdout writeFormat: @"☇ %@", URL.string]; + _length = 0; + return true; } - (void)client: (OFHTTPClient *)client didFailWithException: (id)e @@ -727,11 +729,11 @@ [_output writeBuffer: buffer length: length]; _progressBar.received = _received; - if (response.atEndOfStream || (_length >= 0 && _received >= _length)) { + if (response.atEndOfStream) { [_progressBar stop]; [_progressBar draw]; [_progressBar release]; _progressBar = nil; @@ -765,14 +767,13 @@ [of_stdout writeFormat: @" ➜ %d\n", statusCode]; if (type == nil) type = OF_LOCALIZED(@"type_unknown", @"unknown"); - if (lengthString != nil) + if (lengthString != nil) { _length = lengthString.decimalValue; - if (_length >= 0) { if (_resumedFrom + _length >= GIBIBYTE) { lengthString = [OFString stringWithFormat: @"%,.2f", (float)(_resumedFrom + _length) / GIBIBYTE]; lengthString = OF_LOCALIZED(@"size_gib", @@ -923,12 +924,11 @@ OFString *URLString = nil; OFURL *URL; OFMutableDictionary *clientHeaders; OFHTTPRequest *request; - _length = -1; - _received = _resumedFrom = 0; + _received = _length = _resumedFrom = 0; if (_output != of_stdout) [_output release]; _output = nil;