diff --git a/build/python312/local.mog b/build/python312/local.mog index f74f9489d9..dbb738cae8 100644 --- a/build/python312/local.mog +++ b/build/python312/local.mog @@ -44,9 +44,7 @@ license LICENSE license=PSFv2 set mediator-version $(PYTHONVER)> -# Let the default mediator priority take effect while the highest version is -# the default. -# set mediator-priority vendor> + set mediator-priority vendor> # Add mediated link for /usr/bin/python and python-config link path=usr/bin/python target=python3 diff --git a/build/python313/README b/build/python313/README new file mode 100644 index 0000000000..ab55408d02 --- /dev/null +++ b/build/python313/README @@ -0,0 +1,60 @@ + +Python module dependencies +-------------------------- + +setuptools + pip (and pip depends on setuptools! See "Bootstrap" below) + +pkg + cryptography + setuptools-rust + semantic-version + typing-extensions + tomli + six + cffi + pycparser + asn1crypto + idna + jsonrpclib + jsonschema + attrs + pyrsistent + js-regex + orjson + pycurl + rapidjson + coverage + pyopenssl + cryptography + +bhyve (brand) + pyyaml + +glib2 + meson + packaging + + +Bootstrap +--------- + +To bootstrap modules for a new python version, build 'setuptools' and 'pip' +with the '-f bootstrap' flag, and install these bootstrap packages: + + for m in setuptools pip; do + { cd $m; ./build.sh -f bootstrap -blt; } + done + pfexec pkg install {pip,setuptools}-3XX-bootstrap + +then build again, without the bootstrap flag: + + for m in setuptools pip; do + { cd $m; ./build.sh -blt; } + done + +and finally switch out the bootstrap packages for the real ones: + + pfexec pkg install --reject pip-3XX-bootstrap pip-3XX + pfexec pkg install --reject setuptools-3XX-bootstrap setuptools-3XX + diff --git a/build/python313/build.sh b/build/python313/build.sh new file mode 100755 index 0000000000..9492b77c76 --- /dev/null +++ b/build/python313/build.sh @@ -0,0 +1,202 @@ +#!/usr/bin/bash +# +# {{{ CDDL HEADER +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# }}} +# +# Copyright 2024 OmniOS Community Edition (OmniOSce) Association. +# +. ../../lib/build.sh + +PROG=Python +VER=3.13.1 +PKG=runtime/python-313 +MVER=${VER%.*} +SUMMARY="$PROG $MVER" +DESC="$SUMMARY" + +BUILD_DEPENDS_IPS=" + developer/build/autoconf + developer/pkg-config + ooce/developer/autoconf-archive +" +RUN_DEPENDS_IPS=" + compress/bzip2 + database/sqlite-3 + library/expat + library/libffi + library/libxml2 + library/ncurses + library/readline + library/security/openssl-3 + library/zlib + system/library/gcc-runtime + developer/object-file +" +XFORM_ARGS="-D PYTHONVER=$MVER" + +HARDLINK_TARGETS=" + usr/bin/python$MVER +" +SKIP_RTIME_CHECK=1 +NO_SONAME_EXPECTED=1 + +set_python_version $MVER +set_arch 64 + +# To expose the CMSG_ macros and new recvmsg() semantics for the socket module +set_standard XPG6 + +# Save arguments to the stack so that the mdb/pstack plugin can find them. +CFLAGS[amd64]+=" -msave-args" +CFLAGS[aarch64]+=" -mtls-dialect=trad" + +export CCSHARED="-fPIC" +CPPFLAGS+=" -I/usr/include/ncurses" +export DFLAGS=-64 +MAKE_ARGS=" + DFLAGS=-64 + DESTSHARED=/usr/lib/python$MVER/lib-dynload +" +MAKE_INSTALL_ARGS=DESTSHARED=/usr/lib/python$MVER/lib-dynload + +CONFIGURE_OPTS=" + --enable-shared + --with-system-expat + --enable-ipv6 + --without-ensurepip +" + +CONFIGURE_OPTS[amd64]+=" + --enable-optimizations + --with-dtrace +" + +CONFIGURE_OPTS[aarch64]+=" + --build=${TRIPLETS[amd64]%.*} + --with-build-python=$PYTHON + ac_cv_file__dev_ptmx=yes + ac_cv_file__dev_ptc=no +" + +# See https://bugs.python.org/issue25003 +# There is (was?) a bug in Python which massively slowed down os.urandom() +# due to the introduction of a call to getentropy() which blocks until there +# is sufficient entropy, and this affected pkg(7) significantly. +# We tell Python that we don't have getentropy(); Solaris does the same. +CONFIGURE_OPTS+=" ac_cv_func_getentropy=no " + +# hstrerror() is present in -lresolv, but if configure decides it's available +# then it uses it in a number of modules, which then don't link. +# Versions of Python < 3.11 did not detect this function but the new checks +# in 3.11 are less robust and think it's there even when it isn't. +# https://github.com/python/cpython/issues/89886#issuecomment-1106100113 +CONFIGURE_OPTS+=" ac_cv_func_hstrerror=no" + +# XXX +CONFIGURE_OPTS+=" ac_cv_readline_rl_startup_hook_takes_args=no" + +export DTRACE_CPP=$GCCPATH/bin/cpp + +CURSES_CFLAGS="-DHAVE_NCURSESW -D_XOPEN_SOURCE_EXTENDED" +LIBREADLINE_LIBS="-zrecord -lreadline -lncurses" +export CURSES_CFLAGS LIBREADLINE_LIBS + +build_init() { + typeset s=${SYSROOT[aarch64]} + + addpath PKG_CONFIG_PATH[aarch64] $s/usr/lib/pkgconfig + CONFIGURE_OPTS[aarch64]+=" --with-openssl=$s/usr" +} + +pre_configure() { + typeset arch="$1" + + save_variable CC + + PKG_CONFIG_PATH="${PKG_CONFIG_PATH[$arch]}" \ + PANEL_LIBS=`pkg-config --libs panel` + export PANEL_LIBS + + ! cross_arch $arch && return + + CC+=" --sysroot=${SYSROOT[$arch]}" +} + +post_configure() { + restore_variable CC +} + +post_install() { + python_compile \ + -o0 -o1 -o2 \ + -x 'bad_coding|badsyntax|site-packages|lib2to3/tests/data' +} + +TESTSUITE_SED=" + 1,/Tests result:/ { + /Tests result:/p + d + } + / slowest tests:/,/^$/d + /Total duration/d + /^make:/d +" + +launch_testsuite() { + # Test selection + EXTRATESTOPTS="-uall,-audio,-gui,-largefile,-network -w" + EXTRATESTOPTS+=" --ignorefile $SRCDIR/files/test.exclude" + export EXTRATESTOPTS + if [ -z "$SKIP_TESTSUITE" ] && ( [ -n "$BATCH" ] || ask_to_testsuite ); then + # The compilall test (rightly) gets upset if we force timestamps + save_variable FORCE_PYC_TIMESTAMP + unset FORCE_PYC_TIMESTAMP + BATCH=1 run_testsuite "$@" $logf + for dir in $TMPDIR/$BUILDDIR; do + pushd $dir >/dev/null || logerr "chdir $dir" + $PFEXEC $MAKE test TESTOPTS=test_dtrace | tee -a $logf + # Reset ownership on the python cache directories/files which will + # have been created with root ownership. + $PFEXEC chown -R "`stat -c %U $SRCDIR`" . + popd >/dev/null + done + sed "$TESTSUITE_SED" < $logf > $SRCDIR/testsuite-d.log + [ -s $SRCDIR/testsuite-d.log ] \ + || echo "dtrace tests have failed" >> $SRCDIR/testsuite-d.log +} + +init +download_source $PROG $PROG $VER +patch_source +prep_build autoconf -autoreconf +build +launch_testsuite +test_dtrace +make_package +clean_up + +# Vim hints +# vim:ts=4:sw=4:et:fdm=marker diff --git a/build/python313/common.mog b/build/python313/common.mog new file mode 100644 index 0000000000..fd5f969bce --- /dev/null +++ b/build/python313/common.mog @@ -0,0 +1,33 @@ +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. + +# Copyright 2021 OmniOS Community Edition (OmniOSce) Association. + +# Newer setup tools creates entry point scripts which do things like: +# +# try: +# from importlib import metadata +# except ImportError: # for Python<3.8 +# import importlib_metadata as metadata +# +# This causes the IPS dependency resolver to assume that both importlib and +# importlib_metadata should exist, when in fact one is for Python < 3.8 and +# one is for later versions. Depending on the python version, one or the other +# will fail to be found. +# +# Therefore we bypass resolution of these dependencies. + + set pkg.depend.bypass-generate .*metadata.* > + +# XXX - due to cross compilation in the virtual environment, scripts end up +# with a venv shebang. This needs fixing but for now allow the failed +# dependency. +$(aarch64_ONLY) set pkg.depend.bypass-generate python> + diff --git a/build/python313/common.sh b/build/python313/common.sh new file mode 100644 index 0000000000..0ecfd60baf --- /dev/null +++ b/build/python313/common.sh @@ -0,0 +1,55 @@ +# {{{ CDDL HEADER +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# }}} + +# Copyright 2024 OmniOS Community Edition (OmniOSce) Association. + +set_python_version 3.13 + +PYVER=$PYTHONVER # 3.13 +PYMVER=${PYTHONVER%%.*} # 3 +SPYVER=${PYTHONVER//./} # 313 + +RUN_DEPENDS_IPS="runtime/python-$SPYVER " +XFORM_ARGS=" + -D PYTHONVER=$PYVER + -D PYTHONLIB=${PYTHONLIB#/}/python$PYVER + -D PYVER=$PYVER + -D PYMVER=$PYMVER + -D SPYVER=$SPYVER +" + +PKGDIFF_HELPER=' + s:(vendor-packages/[^-]*)-[0-9.]*:\1-VERSION:g +' + +# Python modules ship shared objects with no SONAME +NO_SONAME_EXPECTED=1 + +# Use an extra directory level for building each module since there can be +# multiple versions of python being built in parallel and if they are built +# in the same directory then they will clobber each other. + +# Do this now, before changing the temporary base +init_repos + +TMPDIR+="/python$PYVER" +DTMPDIR+="/python$PYVER" +BASE_TMPDIR=$TMPDIR + +PEP518OPTS+=" --ignore-installed" + +# Use the same python version for dependency resolution +# XXX +#PKGDEPEND="/usr/bin/python$PYTHONVER -Es $PKGDEPEND" + +# Vim hints +# vim:ts=4:sw=4:et:fdm=marker diff --git a/build/python313/files/ctf.skip b/build/python313/files/ctf.skip new file mode 100644 index 0000000000..840994627e --- /dev/null +++ b/build/python313/files/ctf.skip @@ -0,0 +1 @@ +amd64/libpython3\.so$ diff --git a/build/python313/files/test.exclude b/build/python313/files/test.exclude new file mode 100644 index 0000000000..bb2caa79a1 --- /dev/null +++ b/build/python313/files/test.exclude @@ -0,0 +1,9 @@ +# +# Elements on the python test suite to skip on OmniOS +# +# illumos does not support multiple SCM_RIGHTS messages in a packet +*FDPassSeparate* +# +# Related to wchar_t differences on illumos +*.test_re.ReTests.test_locale_compiled +*.test_re.ReTests.test_locale_caching diff --git a/build/python313/local.mog b/build/python313/local.mog new file mode 100644 index 0000000000..b9b426f755 --- /dev/null +++ b/build/python313/local.mog @@ -0,0 +1,55 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. + +# Copyright 2024 OmniOS Community Edition (OmniOSce) Association. + +license LICENSE license=PSFv2 + +# Drop static library + drop> + +# Remove idle + drop > + drop> + +# Move binaries from usr/bin to private bin + \ + edit path usr/bin usr/lib/python$(PYTHONVER)/bin> + +# Remove test files + drop> + +# Prevent pkgdepend from reporting an error + set pkg.depend.bypass-generate .*> + +# Move libpython3.so and replace with a symlink that can be mediated. The +# mediator is applied below + emit \ + link path=%<1> target=libpython$(PYTHONVER)-stub.so > + edit path 3 $(PYTHONVER)-stub> + +# Set mediators on links in shared directories + set mediator python3> + set mediator python3> + set mediator python3> + + set mediator-version $(PYTHONVER)> + +# set mediator-priority vendor> + +# Add mediated link for /usr/bin/python and python-config +link path=usr/bin/python target=python3 +link path=usr/bin/python-config target=python3-config + set mediator python> + set mediator-version 3> + set mediator-priority vendor> + diff --git a/build/python313/patches/asyncio-watcher.patch b/build/python313/patches/asyncio-watcher.patch new file mode 100644 index 0000000000..a98f40afcb --- /dev/null +++ b/build/python313/patches/asyncio-watcher.patch @@ -0,0 +1,18 @@ +Asyncio watcher 'MultiLoopChildWatcher' currently doesn't work well +on Solaris and can freeze the event loop indefinitely. + +This was reported upstream: +https://bugs.python.org/issue37573 + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/asyncio/unix_events.py a/Lib/asyncio/unix_events.py +--- a~/Lib/asyncio/unix_events.py 1970-01-01 00:00:00 ++++ a/Lib/asyncio/unix_events.py 1970-01-01 00:00:00 +@@ -30,7 +30,7 @@ __all__ = ( + 'SelectorEventLoop', + 'AbstractChildWatcher', 'SafeChildWatcher', + 'FastChildWatcher', 'PidfdChildWatcher', +- 'MultiLoopChildWatcher', 'ThreadedChildWatcher', ++ 'ThreadedChildWatcher', + 'DefaultEventLoopPolicy', + 'EventLoop', + ) diff --git a/build/python313/patches/autoconf-macros.patch b/build/python313/patches/autoconf-macros.patch new file mode 100644 index 0000000000..2efc598627 --- /dev/null +++ b/build/python313/patches/autoconf-macros.patch @@ -0,0 +1,17 @@ + +As of version 3.9.3, python stopped bundling some autoconf m4 macros and +started relying on the 'autoconf-archive' package being installed and +in standard system paths. Yes, in a minor release update, really. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/configure.ac a/configure.ac +--- a~/configure.ac 1970-01-01 00:00:00 ++++ a/configure.ac 1970-01-01 00:00:00 +@@ -16,6 +16,8 @@ AC_PREREQ([2.71]) + + AC_INIT([python],[PYTHON_VERSION],[https://github.com/python/cpython/issues/]) + ++AC_CONFIG_MACRO_DIRS([/opt/ooce/autoconf-archive/share/aclocal]) ++ + m4_ifdef( + [AX_C_FLOAT_WORDS_BIGENDIAN], + [], diff --git a/build/python313/patches/cgiserver.patch b/build/python313/patches/cgiserver.patch new file mode 100644 index 0000000000..d84d921736 --- /dev/null +++ b/build/python313/patches/cgiserver.patch @@ -0,0 +1,45 @@ +Fixes Python CGI being confused about binary files (bugs 31546357 & 31936635). + +Upstream issue: +https://bugs.python.org/issue27777 + +WIP patch taken from upstream is still buggy, so we use +this in-house developed one. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/cgi.py a/Lib/cgi.py +--- a~/Lib/cgi.py 1970-01-01 00:00:00 ++++ a/Lib/cgi.py 1970-01-01 00:00:00 +@@ -705,7 +705,10 @@ class FieldStorage: + if not data: + self.done = -1 + break +- self.file.write(data) ++ if self._binary_file: ++ self.file.write(data) # keepindent ++ else: ++ self.file.write(data.decode()) + todo = todo - len(data) + + def read_lines(self): +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_cgi.py a/Lib/test/test_cgi.py +--- a~/Lib/test/test_cgi.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_cgi.py 1970-01-01 00:00:00 +@@ -389,6 +389,18 @@ Larry + self.assertEqual(fs.list[0].name, 'submit-name') + self.assertEqual(fs.list[0].value, 'Larry') + ++ def test_content_length_no_content_disposition(self): ++ body = b'{"test":123}' ++ env = { ++ 'CONTENT_LENGTH': len(body), ++ 'REQUEST_METHOD': 'POST', ++ 'CONTENT_TYPE': 'application/json', ++ 'wsgi.input': BytesIO(body), ++ } ++ ++ form = cgi.FieldStorage(fp=env['wsgi.input'], environ=env) ++ self.assertEqual(form.file.read(), body.decode(form.encoding)) ++ + def test_field_storage_multipart_no_content_length(self): + fp = BytesIO(b"""--MyBoundary + Content-Disposition: form-data; name="my-arg"; filename="foo" diff --git a/build/python313/patches/default-lib-path.patch b/build/python313/patches/default-lib-path.patch new file mode 100644 index 0000000000..e9de63e9b9 --- /dev/null +++ b/build/python313/patches/default-lib-path.patch @@ -0,0 +1,79 @@ +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/ctypes/util.py a/Lib/ctypes/util.py +--- a~/Lib/ctypes/util.py 1970-01-01 00:00:00 ++++ a/Lib/ctypes/util.py 1970-01-01 00:00:00 +@@ -239,43 +239,56 @@ elif os.name == "posix": + elif sys.platform == "sunos5": + + def _findLib_crle(name, is64): +- if not os.path.exists('/usr/bin/crle'): +- return None +- + env = dict(os.environ) + env['LC_ALL'] = 'C' + ++ paths = [] ++ + if is64: +- args = ('/usr/bin/crle', '-64') ++ var = 'LD_LIBRARY_PATH_64' + else: +- args = ('/usr/bin/crle',) ++ var = 'LD_LIBRARY_PATH_32' ++ ++ if (lp := env.get(var)) or (lp := env.get('LD_LIBRARY_PATH')): ++ paths.extend(lp.split(':')) ++ ++ if os.path.exists('/usr/bin/crle'): ++ args = ['/usr/bin/crle'] ++ if is64: ++ args.append('-64') ++ var = 'LD_CONFIG_64' ++ else: ++ var = 'LD_CONFIG_32' ++ ++ if (cfg := env.get(var)) or (cfg := env.get('LD_CONFIG')): ++ args.extend(['-c', cfg]) + +- paths = None + try: +- proc = subprocess.Popen(args, +- stdout=subprocess.PIPE, +- stderr=subprocess.DEVNULL, +- env=env) ++ with subprocess.Popen(args, ++ stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, ++ env=env) as proc: ++ for line in proc.stdout: #WS ++ line = line.strip() #WS ++ if line.startswith( ++ b'Default Library Path (ELF):'): ++ paths.extend( ++ os.fsdecode(line).split()[4].split(':')) ++ break + except OSError: # E.g. bad executable +- return None +- with proc: +- for line in proc.stdout: +- line = line.strip() +- if line.startswith(b'Default Library Path (ELF):'): +- paths = os.fsdecode(line).split()[4] ++ pass + + if not paths: + return None + +- for dir in paths.split(":"): +- libfile = os.path.join(dir, "lib%s.so" % name) ++ for dir in paths: ++ libfile = os.path.join(dir, f'lib{name}.so') + if os.path.exists(libfile): + return libfile + + return None + + def find_library(name, is64 = False): +- return _get_soname(_findLib_crle(name, is64) or _findLib_gcc(name)) ++ return _get_soname(_findLib_crle(name, is64)) + + else: + diff --git a/build/python313/patches/disable_epoll.patch b/build/python313/patches/disable_epoll.patch new file mode 100644 index 0000000000..d1706bc9cd --- /dev/null +++ b/build/python313/patches/disable_epoll.patch @@ -0,0 +1,23 @@ + +Without this patch, python detects and uses epoll which only exists in +OmniOS for lx zones and Linux compatibility. It is not quite the same as +the Linux implementation and can cause socket related failures in python. + +There is no nice way to tell configure that we don't have it +(pkgsrc get around this by installing a broken epoll.h into the build + chroot area). + +diff -wpruN --no-dereference '--exclude=*.orig' a~/configure.ac a/configure.ac +--- a~/configure.ac 1970-01-01 00:00:00 ++++ a/configure.ac 1970-01-01 00:00:00 +@@ -5170,8 +5170,8 @@ PY_CHECK_FUNC([symlink], [@%:@include ]) + PY_CHECK_FUNC([fsync], [@%:@include ]) + PY_CHECK_FUNC([fdatasync], [@%:@include ]) +-PY_CHECK_FUNC([epoll_create], [@%:@include ], [HAVE_EPOLL]) +-PY_CHECK_FUNC([epoll_create1], [@%:@include ]) ++PY_CHECK_FUNC([NOepoll_create], [@%:@include ], [HAVE_EPOLL]) ++PY_CHECK_FUNC([NOepoll_create1], [@%:@include ]) + PY_CHECK_FUNC([kqueue],[ + #include + #include diff --git a/build/python313/patches/dont-use-ccs.patch b/build/python313/patches/dont-use-ccs.patch new file mode 100644 index 0000000000..1e65c6af7f --- /dev/null +++ b/build/python313/patches/dont-use-ccs.patch @@ -0,0 +1,12 @@ +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/ctypes/util.py a/Lib/ctypes/util.py +--- a~/Lib/ctypes/util.py 1970-01-01 00:00:00 ++++ a/Lib/ctypes/util.py 1970-01-01 00:00:00 +@@ -168,7 +168,7 @@ elif os.name == "posix": + return None + + try: +- proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f), ++ proc = subprocess.Popen(("/usr/bin/dump", "-Lpv", f), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. command not found diff --git a/build/python313/patches/encoding-alias.patch b/build/python313/patches/encoding-alias.patch new file mode 100644 index 0000000000..28ec84b43d --- /dev/null +++ b/build/python313/patches/encoding-alias.patch @@ -0,0 +1,38 @@ +Add missing encoding aliases. It may be contributed upstream at some point, +but the suitability (or lack thereof) has not yet been determined. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/encodings/aliases.py a/Lib/encodings/aliases.py +--- a~/Lib/encodings/aliases.py 1970-01-01 00:00:00 ++++ a/Lib/encodings/aliases.py 1970-01-01 00:00:00 +@@ -79,6 +79,7 @@ aliases = { + + # cp1251 codec + '1251' : 'cp1251', ++ 'ansi_1251' : 'cp1251', + 'windows_1251' : 'cp1251', + + # cp1252 codec +@@ -234,6 +235,7 @@ aliases = { + 'u_jis' : 'euc_jp', + + # euc_kr codec ++ '5601' : 'euc_kr', + 'euckr' : 'euc_kr', + 'korean' : 'euc_kr', + 'ksc5601' : 'euc_kr', +@@ -484,6 +486,7 @@ aliases = { + 'shiftjis' : 'shift_jis', + 'sjis' : 'shift_jis', + 's_jis' : 'shift_jis', ++ 'pck' : 'shift_jis', + + # shift_jis_2004 codec + 'shiftjis2004' : 'shift_jis_2004', +@@ -500,6 +503,7 @@ aliases = { + 'tis_620_0' : 'tis_620', + 'tis_620_2529_0' : 'tis_620', + 'tis_620_2529_1' : 'tis_620', ++ 'tis620.2533' : 'tis_620', + 'iso_ir_166' : 'tis_620', + + # utf_16 codec diff --git a/build/python313/patches/gethostbyname.patch b/build/python313/patches/gethostbyname.patch new file mode 100644 index 0000000000..554af30301 --- /dev/null +++ b/build/python313/patches/gethostbyname.patch @@ -0,0 +1,42 @@ +Attempting to detect whether we have a gethostbyname_r() with 6 arguments +is fatal with gcc14. We jump straight to the 5 argument check which is +correct when building with XPG6. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/configure.ac a/configure.ac +--- a~/configure.ac 1970-01-01 00:00:00 ++++ a/configure.ac 1970-01-01 00:00:00 +@@ -5745,26 +5745,8 @@ AH_TEMPLATE([HAVE_GETHOSTBYNAME_R], + + AC_CHECK_FUNC([gethostbyname_r], + [AC_DEFINE([HAVE_GETHOSTBYNAME_R]) +- AC_MSG_CHECKING([gethostbyname_r with 6 args]) + OLD_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS $MY_CPPFLAGS $MY_THREAD_CPPFLAGS $MY_CFLAGS" +- AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ +-# include +- ]], [[ +- char *name; +- struct hostent *he, *res; +- char buffer[2048]; +- int buflen = 2048; +- int h_errnop; +- +- (void) gethostbyname_r(name, he, buffer, buflen, &res, &h_errnop) +- ]])],[ +- AC_DEFINE([HAVE_GETHOSTBYNAME_R]) +- AC_DEFINE([HAVE_GETHOSTBYNAME_R_6_ARG], [1], +- [Define this if you have the 6-arg version of gethostbyname_r().]) +- AC_MSG_RESULT([yes]) +- ],[ +- AC_MSG_RESULT([no]) + AC_MSG_CHECKING([gethostbyname_r with 5 args]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + # include +@@ -5803,7 +5785,6 @@ AC_CHECK_FUNC([gethostbyname_r], + AC_MSG_RESULT([no]) + ]) + ]) +- ]) + CFLAGS=$OLD_CFLAGS + ], [ + AC_CHECK_FUNCS([gethostbyname]) diff --git a/build/python313/patches/illumos.patch b/build/python313/patches/illumos.patch new file mode 100644 index 0000000000..59b01a69cc --- /dev/null +++ b/build/python313/patches/illumos.patch @@ -0,0 +1,59 @@ +diff -wpruN --no-dereference '--exclude=*.orig' a~/configure.ac a/configure.ac +--- a~/configure.ac 1970-01-01 00:00:00 ++++ a/configure.ac 1970-01-01 00:00:00 +@@ -317,6 +317,7 @@ then + # ac_sys_system and ac_sys_release are used for setting + # a lot of different things including 'define_xopen_source' + # in the case statement below. ++ ac_sys_release= + case "$host" in + *-*-linux-android*) + ac_sys_system=Linux-android +@@ -339,12 +340,15 @@ then + *-*-wasi*) + ac_sys_system=WASI + ;; ++ *-*-solaris*) ++ ac_sys_system=SunOS ++ ac_sys_release=5.11 ++ ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" + AC_MSG_ERROR([cross build not supported for $host]) + esac +- ac_sys_release= + else + ac_sys_system=`uname -s` + if test "$ac_sys_system" = "AIX" \ +@@ -452,6 +456,15 @@ AC_ARG_ENABLE([universalsdk], + UNIVERSALSDK= + enable_universalsdk= + ;; ++ *-*-solaris*) ++ case "$host_cpu" in ++ aarch64*) ++ _host_cpu=arm ++ ;; ++ *) ++ _host_cpu=$host_cpu ++ esac ++ ;; + *) + UNIVERSALSDK=$enableval + if test ! -d "${UNIVERSALSDK}" +@@ -777,6 +790,14 @@ if test "$cross_compiling" = yes; then + wasm32-*-* | wasm64-*-*) + _host_ident=$host_cpu + ;; ++ *-*-solaris2) ++ case "$host_cpu" in ++ aarch64) ++ _host_ident=aarch64-unknown-solaris2 ;; ++ *) ++ AC_MSG_ERROR([unknown illumos platform]) ;; ++ esac ++ ;; + *) + # for now, limit cross builds to known configurations + MACHDEP="unknown" diff --git a/build/python313/patches/locale-encoding.patch b/build/python313/patches/locale-encoding.patch new file mode 100644 index 0000000000..42484a92d9 --- /dev/null +++ b/build/python313/patches/locale-encoding.patch @@ -0,0 +1,104 @@ +Python on Solaris doesn't handle non UTF-8 locales because of the way they are +encoded. The wchar_t encoding used for stored symbols is not standardized. While +on Linux symbols from all encodings will be mapped to their UTF-8 values, this +is not the case on Solaris, where only UTF-8 locales work like that; other +encodings can use any arbitrary value. Since Python expects no value to be +higher than the maximum valid code point in Unicode (which is U+10FFFF), it +breaks on Solaris when non UTF-8 locale is used. See bug 31790476. + +To fix this, we have to convert given wchar_t to utf32 each time locale is not +UTF-8 encoded (or ASCII, which can safely be ignored). + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Include/unicodeobject.h a/Include/unicodeobject.h +--- a~/Include/unicodeobject.h 1970-01-01 00:00:00 ++++ a/Include/unicodeobject.h 1970-01-01 00:00:00 +@@ -89,6 +89,11 @@ Copyright (c) Corporation for National R + # endif + #endif + ++#if defined(__sun) && defined(__SVR4) ++# include ++# include ++#endif ++ + /* Py_UCS4 and Py_UCS2 are typedefs for the respective + unicode representations. */ + typedef uint32_t Py_UCS4; +diff -wpruN --no-dereference '--exclude=*.orig' a~/Objects/unicodeobject.c a/Objects/unicodeobject.c +--- a~/Objects/unicodeobject.c 1970-01-01 00:00:00 ++++ a/Objects/unicodeobject.c 1970-01-01 00:00:00 +@@ -1956,6 +1956,15 @@ unicode_char(Py_UCS4 ch) + return unicode; + } + ++#if defined(__sun) && defined(__SVR4) ++/* Detect whether currently used locale uses UTF compatible encoding. */ ++int codeset_is_utf8_compatible() ++{ ++ char* res = nl_langinfo(CODESET); ++ return !(strcmp(res, "UTF-8") && strcmp(res, "646")); ++} ++#endif ++ + PyObject * + PyUnicode_FromWideChar(const wchar_t *u, Py_ssize_t size) + { +@@ -1992,6 +2001,58 @@ PyUnicode_FromWideChar(const wchar_t *u, + return unicode; + } + #endif ++ ++#if defined(__sun) && defined(__SVR4) ++ /* Check whether current locale uses UTF to encode symbols */ ++ if (!codeset_is_utf8_compatible()) { ++ ++ /* Given 'u' might not be NULL terminated (size smaller than its ++ length); copy and terminate part we are interested in. */ ++ wchar_t* substr = PyMem_RawMalloc((size + 1) * sizeof(wchar_t)); ++ memcpy(substr, u, size * sizeof(wchar_t)); ++ substr[size] = 0; ++ ++ /* Convert given wide-character string to a character string */ ++ size_t buffsize = wcstombs(NULL, substr, 0) + 1; ++ if (buffsize == (size_t)-1) { ++ PyMem_RawFree(substr); ++ PyErr_Format(PyExc_ValueError, "wcstombs() conversion failed"); ++ return NULL; ++ } ++ ++ char* buffer = PyMem_RawMalloc(buffsize * sizeof(char)); ++ size_t res = wcstombs(buffer, substr, buffsize); ++ assert(res == buffsize - 1); ++ ++ /* Convert character string to UTF32 encoded char32_t string. ++ Since wchar_t and char32_t have the same size on Solaris and one ++ wchar_t symbol corresponds to one UTF32 value, we can safely ++ reuse this buffer and skip additional allocation. */ ++ char32_t* c32 = (char32_t*) substr; ++ mbstate_t state = {0}; ++ ++ int i = 0; ++ char* ptr = buffer; ++ char* end = ptr + res + 1; ++ while (res = mbrtoc32(&(c32[i]), ptr, end - ptr, &state)) { ++ if (res == (size_t)-1 || res == (size_t)-2 || res == (size_t)-3) { ++ PyMem_RawFree(c32); ++ PyMem_RawFree(buffer); ++ PyErr_Format(PyExc_ValueError, ++ "mbrtoc32() conversion failed with error code: %d", ++ res); ++ return NULL; ++ } ++ ptr += res; ++ i ++; ++ } ++ PyMem_RawFree(buffer); ++ ++ PyObject *unicode = _PyUnicode_FromUCS4(c32, size); ++ PyMem_RawFree(c32); ++ return unicode; ++ } ++#endif + + /* Single character Unicode objects in the Latin-1 range are + shared when using this constructor */ diff --git a/build/python313/patches/mod-posix-sched_priority.patch b/build/python313/patches/mod-posix-sched_priority.patch new file mode 100644 index 0000000000..4df62a2094 --- /dev/null +++ b/build/python313/patches/mod-posix-sched_priority.patch @@ -0,0 +1,33 @@ + +On illumos, scheduling priorities can be less than 0 +(For example, the default range is -60 to 60 for the TS and FSS schedulers) + +However, -1 alongside EINVAL represents an error. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/posixmodule.c a/Modules/posixmodule.c +--- a~/Modules/posixmodule.c 1970-01-01 00:00:00 ++++ a/Modules/posixmodule.c 1970-01-01 00:00:00 +@@ -8097,7 +8097,11 @@ os_sched_get_priority_max_impl(PyObject + int max; + + max = sched_get_priority_max(policy); ++#ifdef __sun ++ if (max == -1 && errno == EINVAL) ++#else + if (max < 0) ++#endif + return posix_error(); + return PyLong_FromLong(max); + } +@@ -8116,7 +8120,11 @@ os_sched_get_priority_min_impl(PyObject + /*[clinic end generated code: output=7595c1138cc47a6d input=21bc8fa0d70983bf]*/ + { + int min = sched_get_priority_min(policy); ++#ifdef __sun ++ if (min == -1 && errno == EINVAL) ++#else + if (min < 0) ++#endif + return posix_error(); + return PyLong_FromLong(min); + } diff --git a/build/python313/patches/mod-shutil-sendfile.patch b/build/python313/patches/mod-shutil-sendfile.patch new file mode 100644 index 0000000000..69eff3afb5 --- /dev/null +++ b/build/python313/patches/mod-shutil-sendfile.patch @@ -0,0 +1,17 @@ + +Without this patch, shutil.copyfile() will only use sendfile() on Linux +It works on illumos too, and this fixes a problem with tests that rely +on sendfile being involved. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/shutil.py a/Lib/shutil.py +--- a~/Lib/shutil.py 1970-01-01 00:00:00 ++++ a/Lib/shutil.py 1970-01-01 00:00:00 +@@ -48,7 +48,7 @@ COPY_BUFSIZE = 1024 * 1024 if _WINDOWS e + # This should never be removed, see rationale in: + # https://bugs.python.org/issue43743#msg393429 + _USE_CP_SENDFILE = (hasattr(os, "sendfile") +- and sys.platform.startswith(("linux", "android"))) ++ and sys.platform.startswith(("linux", "android", "sunos"))) + _HAS_FCOPYFILE = posix and hasattr(posix, "_fcopyfile") # macOS + + # CMD defaults in Windows 10 diff --git a/build/python313/patches/module-dlpi.patch b/build/python313/patches/module-dlpi.patch new file mode 100644 index 0000000000..d640a525bd --- /dev/null +++ b/build/python313/patches/module-dlpi.patch @@ -0,0 +1,1346 @@ +This patch provides Python dlpi support. It may be contributed upstream at +some point, but the suitability (or lack thereof) has not yet been determined. +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/dlpitest.py a/Lib/test/dlpitest.py +--- a~/Lib/test/dlpitest.py 1970-01-01 00:00:00 ++++ a/Lib/test/dlpitest.py 1970-01-01 00:00:00 +@@ -0,0 +1,96 @@ ++#!/usr/bin/python3 ++ ++import dlpi ++import sys ++import time ++import struct ++ ++#test listlink ++linklist = dlpi.listlink() ++print("Found %d links:" % len(linklist)) ++print(linklist) ++ ++#pick up the first data link for below testing ++linkname = linklist[0] ++ ++#open link ++print("opening link: " + linkname + "...") ++testlink = dlpi.link(linkname) ++ ++#read some info of testlink ++print("linkname is %s" % testlink.get_linkname()) ++print("link fd is %d" % testlink.get_fd()) ++mactype = testlink.get_mactype() ++print("dlpi mactype is %d" % mactype) ++print("after convert:") ++print("\tmactype is %s" % dlpi.mactype(mactype)) ++print("\tiftype is %d" % dlpi.iftype(mactype)) ++print("\tarptype is %d" % dlpi.arptype(mactype)) ++bcastaddr = testlink.get_bcastaddr() ++print("broadcast addr is: ", end=' ') ++print(struct.unpack("BBBBBB",bcastaddr)) ++physaddr = testlink.get_physaddr(dlpi.FACT_PHYS_ADDR) ++print("factory physical address is: ", end=' ') ++print(struct.unpack("BBBBBB",physaddr)) ++print("current timeout value is %d" % testlink.get_timeout()) ++print("sdu is:", end=' ') ++print(testlink.get_sdu()) ++print("qos select is:", end=' ') ++print(testlink.get_qos_select()) ++print("qos range is:", end=' ') ++print(testlink.get_qos_range()) ++ ++#set some config value of testlink and read them again ++print("setting current physical addr to aa:0:10:13:27:5") ++testlink.set_physaddr(b"\xaa\0\x10\x13\x27\5") ++physaddr = testlink.get_physaddr(dlpi.CURR_PHYS_ADDR) ++print("current physical addr is: ", end=' ') ++print(struct.unpack("BBBBBB",physaddr)) ++print("set timeout value to 6...") ++testlink.set_timeout(6) ++print("timeout value is %d" % testlink.get_timeout()) ++ ++#test enable/disable multicast ++print("enable/disable multicast address 1:0:5e:0:0:5") ++testlink.enabmulti('\1\0\x5e\0\0\5') ++testlink.disabmulti('\1\0\x5e\0\0\5') ++ ++#test bind ++print("binding to SAP 0x9000...") ++testlink.bind(0x9000) ++print("sap is %x" % testlink.get_sap()) ++print("state is: %d" % testlink.get_state()) ++ ++#test send ++print("sending broadcast loopback packet...") ++testlink.send(bcastaddr, '\0\1\2\3\4\5') ++ ++#test notify functionality ++arg = "notification callback arg" ++def notify(arg, notes, value): ++ print("NOTE_PROMISC_ON_PHYS notification received with arg: '%s'" % arg) ++print("enabled notification on NOTE_PROMISC_ON_PHYS") ++id = testlink.enabnotify(dlpi.NOTE_PROMISC_ON_PHYS, notify, arg) #enable notification ++testlink.promiscon() #trigger the event (will be seen while receiving pkt below) ++ ++#test receive ++print("testing receiving...") ++try: ++ testlink.recv(0, 0) #should see NOTE_PROMISC_ON_PHYS event here ++except dlpi.error as err: ++ errnum, errinfo = err ++ if errnum == 10006: ++ pass #timeout error is expected here ++ else: #test fails if reach here ++ print("test failed", end=' ') ++ print(errnum, end=' ') ++ print(err) ++ ++testlink.promiscoff() ++testlink.disabnotify(id) #disable notification ++ ++#test unbind ++print("unbinding...") ++testlink.unbind() ++print("sap is %x" % testlink.get_sap()) ++print("state is: %d" % testlink.get_state()) +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/Setup.local a/Modules/Setup.local +--- a~/Modules/Setup.local 1970-01-01 00:00:00 ++++ a/Modules/Setup.local 1970-01-01 00:00:00 +@@ -1,3 +1,4 @@ + # Edit this file for local setup changes + + ucred ucred.c -ltsol ++dlpi dlpimodule.c -ldlpi +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/dlpimodule.c a/Modules/dlpimodule.c +--- a~/Modules/dlpimodule.c 1970-01-01 00:00:00 ++++ a/Modules/dlpimodule.c 1970-01-01 00:00:00 +@@ -0,0 +1,1232 @@ ++/* ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ * DEALINGS IN THE SOFTWARE. ++ * ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. ++ */ ++ ++#define PY_SSIZE_T_CLEAN ++ ++#include ++#include ++#include ++ ++typedef struct { ++ PyObject_HEAD ++ dlpi_handle_t dlpihdl; ++} pylink_t; ++ ++typedef struct { ++ PyObject *pyfunc; ++ PyObject *pyarg; ++} callback_data_t; ++ ++/* ++ * dlpi_err: the only exception raised for libdlpi related error. ++ * The accompanying value is: ++ * (dlpi_error_number, string), when it's a dlpi specific error, ++ * or, (DL_SYSERR, errno, string), when it's coming from a system call. ++ */ ++static PyObject *dlpi_err; ++ ++static void ++dlpi_raise_exception(int err) ++{ ++ PyObject *e = NULL; ++ ++ if (err == DL_SYSERR) { ++ e = Py_BuildValue("(iis)", DL_SYSERR, errno, strerror(errno)); ++ } else { ++ e = Py_BuildValue("(is)", err, dlpi_strerror(err)); ++ } ++ if (e != NULL) { ++ PyErr_SetObject(dlpi_err, e); ++ Py_DECREF(e); ++ } ++} ++ ++PyDoc_STRVAR(link_doc, ++ "link(linkname[, flags]) -> link object\n" ++ "\n" ++ "Open linkname with specified flags.\n" ++ "Three flags are supported: PASSIVE, RAW, NATIVE.\n" ++ "And these flags can be bitwise-OR'ed together(default flag is 0).\n" ++ "You need sys_net_rawaccess privilege to open a link.\n" ++ "See dlpi_open(3DLPI).\n" ++); ++static int ++link_init(PyObject *self, PyObject *args, PyObject *kwds) ++{ ++ uint_t flags = 0; ++ dlpi_handle_t dh; ++ char *linkname; ++ int rval; ++ static char *keywords[] = {"linkname", "flags", NULL}; ++ pylink_t *link = (pylink_t *)self; ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|I", keywords, ++ &linkname, &flags)) ++ return (-1); ++ ++ if ((rval = dlpi_open(linkname, &dh, flags)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (-1); ++ } ++ ++ link->dlpihdl = dh; ++ ++ return (0); ++} ++ ++static void ++link_dealloc(pylink_t *link) ++{ ++ if (link->dlpihdl != NULL) ++ dlpi_close(link->dlpihdl); ++ Py_TYPE(link)->tp_free((PyObject *)link); ++} ++ ++PyDoc_STRVAR(bind_doc, ++ "bind(sap) -> unsigned int\n" ++ "\n" ++ "Attempts to bind the link to specified SAP, or ANY_SAP.\n" ++ "Returns the SAP that the function actually bound to, which\n" ++ "could be different from the SAP requested.\n" ++ "See dlpi_bind(3DLPI).\n" ++); ++static PyObject * ++link_bind(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ uint_t sap = 0, boundsap = 0; ++ static char *keywords[] = {"sap", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I", keywords, &sap)) ++ return (NULL); ++ ++ if ((rval = dlpi_bind(link->dlpihdl, sap, &boundsap)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("I", boundsap)); ++} ++ ++PyDoc_STRVAR(unbind_doc, ++ "unbind() -> None\n" ++ "\n" ++ "Attempts to unbind the link from previously bound sap.\n" ++ "See dlpi_unbind(3DLPI).\n" ++); ++static PyObject * ++link_unbind(pylink_t *link) ++{ ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_unbind(link->dlpihdl)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(send_doc, ++ "send(destaddr, message[, sap, minpri, maxpri]) -> None\n" ++ "\n" ++ "Attempts to send message over this link to sap on destaddr.\n" ++ "If SAP is not specified, the bound SAP is used\n" ++ "You can also specify priority range (minpri, maxpri).\n" ++ "See dlpi_send(3DLPI).\n" ++); ++static PyObject * ++link_send(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ char *daddr = NULL, *msgbuf = NULL; ++ size_t daddrlen = 0, msglen = 0; ++ t_scalar_t minpri = DL_QOS_DONT_CARE, maxpri = DL_QOS_DONT_CARE; ++ uint_t sap = DLPI_ANY_SAP; ++ dlpi_sendinfo_t ds, *dsp = NULL; ++ static char *keywords[] = ++ {"destaddr", "message", "sap", "minpri", "maxpri", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "y#y#|Iii", keywords, ++ &daddr, &daddrlen, &msgbuf, &msglen, &sap, &minpri, &maxpri)) ++ return (NULL); ++ ++ if ((sap != DLPI_ANY_SAP) || (minpri != DL_QOS_DONT_CARE) || ++ (maxpri != DL_QOS_DONT_CARE)) { ++ ds.dsi_sap = sap; ++ ds.dsi_prio.dl_min = minpri; ++ ds.dsi_prio.dl_max = maxpri; ++ dsp = &ds; ++ } ++ ++ if ((rval = dlpi_send(link->dlpihdl, daddr, daddrlen, ++ msgbuf, msglen, dsp)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(recv_doc, ++ "recv(msglen[, timeout]) -> (string, string), or (None, None)\n" ++ "\n" ++ "Attempts to receive message over this link.\n" ++ "You need to specify the message length for the received message.\n" ++ "And you can specify timeout value in milliseconds.\n" ++ "The default timeout value is -1 (wait forever).\n" ++ "Returns (source address, message data), or (None, None) when error occurs.\n" ++ "See dlpi_recv(3DLPI).\n" ++); ++static PyObject * ++link_recv(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ PyObject *obj; ++ char *saddr = NULL, *msgbuf = NULL; ++ size_t saddrlen = 0, msglen = 0, *saddrlenp = NULL, *msglenp = NULL; ++ int msec = -1; /* block until receive data */ ++ static char *keywords[] = {"msglen", "timeout", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "k|i", ++ keywords, &msglen, &msec)) ++ return (NULL); ++ ++ if (msglen > 0) { ++ msgbuf = malloc(msglen); ++ if (msgbuf == NULL) { ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ saddrlen = DLPI_PHYSADDR_MAX; ++ saddr = malloc(saddrlen); ++ if (saddr == NULL) { ++ dlpi_raise_exception(DL_SYSERR); ++ free(msgbuf); ++ return (NULL); ++ } ++ msglenp = &msglen; ++ saddrlenp = &saddrlen; ++ } ++ ++ if ((rval = dlpi_recv(link->dlpihdl, saddr, saddrlenp, msgbuf, ++ msglenp, msec, NULL)) != DLPI_SUCCESS) { ++ if (msgbuf != NULL) ++ free(msgbuf); ++ if (saddr != NULL) ++ free(saddr); ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ obj = Py_BuildValue("y#y#", saddr, saddrlen, msgbuf, msglen); ++ if (msgbuf != NULL) ++ free(msgbuf); ++ if (saddr != NULL) ++ free(saddr); ++ return (obj); ++} ++ ++PyDoc_STRVAR(disabmulti_doc, ++ "disabmulti(address) -> None\n" ++ "\n" ++ "Disable a specified multicast address on this link.\n" ++ "See dlpi_disabmulti(3DLPI).\n" ++); ++static PyObject * ++link_disabmulti(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ char *addr = NULL; ++ size_t addrlen = 0; ++ static char *keywords[] = {"address", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "y#", keywords, ++ &addr, &addrlen)) ++ return (NULL); ++ ++ if ((addrlen == 0) || (addrlen > DLPI_PHYSADDR_MAX)) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_disabmulti(link->dlpihdl, addr, addrlen)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(enabmulti_doc, ++ "enabmulti(address) -> None\n" ++ "\n" ++ "Enable a specified multicast address on this link.\n" ++ "See dlpi_enabmulti(3DLPI).\n" ++); ++static PyObject * ++link_enabmulti(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ char *addr = NULL; ++ size_t addrlen = 0; ++ static char *keywords[] = {"address", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "y#", keywords, ++ &addr, &addrlen)) ++ return (NULL); ++ ++ if ((addrlen == 0) || (addrlen > DLPI_PHYSADDR_MAX)) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_enabmulti(link->dlpihdl, addr, addrlen)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++static void ++dlpi_callback(dlpi_handle_t hdl, dlpi_notifyinfo_t *ni, void *arg) ++{ ++ callback_data_t *cd = (callback_data_t *)arg; ++ PyObject *arglist, *result; ++ ++ switch (ni->dni_note) { ++ case DL_NOTE_SPEED: ++ arglist = Py_BuildValue("(OII)", ++ cd->pyarg, ni->dni_note, ni->dni_speed); ++ break; ++ case DL_NOTE_SDU_SIZE: ++ arglist = Py_BuildValue("(OII)", ++ cd->pyarg, ni->dni_note, ni->dni_size); ++ break; ++ case DL_NOTE_PHYS_ADDR: ++ arglist = Py_BuildValue("(OIy#)", ++ cd->pyarg, ni->dni_note, ni->dni_physaddr, ++ ni->dni_physaddrlen); ++ break; ++ default: ++ arglist = Py_BuildValue("(OIO)", cd->pyarg, ni->dni_note, ++ Py_None); ++ } ++ ++ result = PyObject_CallObject(cd->pyfunc, arglist); ++ Py_DECREF(arglist); ++ if (result == NULL) { ++ PyErr_Clear(); /* cannot raise error */ ++ } ++ Py_DECREF(result); ++ Py_DECREF(cd->pyfunc); ++ Py_XDECREF(cd->pyarg); ++ free(cd); ++} ++ ++PyDoc_STRVAR(enabnotify_doc, ++ "enabnotify(events, function[, arg]) -> unsigned long\n" ++ "\n" ++ "Enables a notification callback for the set of specified events,\n" ++ "which must be one or more (by a logical OR) events listed as below:\n" ++ "NOTE_LINK_DOWN Notify when link has gone down\n" ++ "NOTE_LINK_UP Notify when link has come up\n" ++ "NOTE_PHYS_ADDR Notify when address changes\n" ++ "NOTE_SDU_SIZE Notify when MTU changes\n" ++ "NOTE_SPEED Notify when speed changes\n" ++ "NOTE_PROMISC_ON_PHYS Notify when PROMISC_PHYS is set\n" ++ "NOTE_PROMISC_OFF_PHYS Notify when PROMISC_PHYS is cleared\n" ++ "Returns a handle for this registration.\n" ++ "See dlpi_enabnotify(3DLPI).\n" ++); ++static PyObject * ++link_enabnotify(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ PyObject *func = NULL, *arg = NULL; ++ callback_data_t *cd; ++ uint_t notes = 0; ++ static char *keywords[] = {"events", "function", "arg", NULL}; ++ dlpi_notifyid_t id; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "IO|O", ++ keywords, ¬es, &func, &arg)) ++ return (NULL); ++ ++ if (!PyCallable_Check(func)) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ cd = malloc(sizeof(callback_data_t)); ++ if (cd == NULL) { ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ Py_INCREF(func); ++ Py_XINCREF(arg); ++ cd->pyfunc = func; ++ cd->pyarg = arg; ++ ++ if ((rval = dlpi_enabnotify(link->dlpihdl, notes, dlpi_callback, ++ cd, &id)) != DLPI_SUCCESS) { ++ free(cd); ++ Py_DECREF(func); ++ Py_XDECREF(arg); ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("k", id)); ++} ++ ++PyDoc_STRVAR(disabnotify_doc, ++ "disabnotify(handle) -> argument object, or None\n" ++ "\n" ++ "Disables the notification registration associated with handle.\n" ++ "You should get this handle by calling enabnotify().\n" ++ "Returns the argument passed in when registering the callback, or None.\n" ++ "See dlpi_disabnotify(3DLPI).\n" ++); ++static PyObject * ++link_disabnotify(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ dlpi_notifyid_t id; ++ callback_data_t *arg; ++ PyObject *pyargsaved; ++ static char *keywords[] = {"handle", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "k", keywords, &id)) ++ return (NULL); ++ ++ if ((rval = dlpi_disabnotify(link->dlpihdl, id, (void **)&arg)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ /* clean up */ ++ pyargsaved = arg->pyarg; ++ Py_XINCREF(pyargsaved); ++ Py_XDECREF(arg->pyarg); ++ Py_DECREF(arg->pyfunc); ++ free(arg); ++ ++ if (pyargsaved != NULL) ++ return (pyargsaved); ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(get_sap_doc, ++ "get_sap() -> unsigned int\n" ++ "\n" ++ "Returns the sap bound to this link.\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_sap(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("I", info.di_sap)); ++} ++ ++PyDoc_STRVAR(get_fd_doc, ++ "get_fd() -> int\n" ++ "\n" ++ "Returns the integer file descriptor that can be used to directly\n" ++ "operate on the link.\n" ++ "See dlpi_fd(3DLPI).\n" ++); ++static PyObject * ++link_get_fd(pylink_t *link) ++{ ++ int fd; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((fd = dlpi_fd(link->dlpihdl)) == -1) { ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("i", fd)); ++} ++ ++PyDoc_STRVAR(get_linkname_doc, ++ "get_linkname() -> string\n" ++ "\n" ++ "Returns the name of the link.\n" ++ "See dlpi_linkname(3DLPI).\n" ++); ++static PyObject * ++link_get_linkname(pylink_t *link) ++{ ++ const char *name = NULL; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((name = dlpi_linkname(link->dlpihdl)) == NULL) { ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("s", name)); ++} ++ ++PyDoc_STRVAR(get_bcastaddr_doc, ++ "get_bcastaddr() -> string, or None\n" ++ "\n" ++ "Returns the broadcast address of the link.\n" ++ "Returns None if the broadcast address is empty.\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_bcastaddr(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ if (info.di_bcastaddrlen == 0) { ++ Py_INCREF(Py_None); ++ return (Py_None); ++ } ++ ++ return (Py_BuildValue("y#", info.di_bcastaddr, info.di_bcastaddrlen)); ++} ++ ++PyDoc_STRVAR(get_physaddr_doc, ++ "get_physaddr(addrtype) -> string, or None\n" ++ "\n" ++ "Addrtype can be any one of the value listed below:\n" ++ "FACT_PHYS_ADDR Factory physical address\n" ++ "CURR_PHYS_ADDR Current physical address\n" ++ "Returns the corresponding physical address of the link.\n" ++ "See dlpi_get_physaddr(3DLPI).\n" ++); ++static PyObject * ++link_get_physaddr(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ char *addr[DLPI_PHYSADDR_MAX]; ++ size_t addrlen = DLPI_PHYSADDR_MAX; ++ static char *keywords[] = {"addrtype", NULL}; ++ uint_t type; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I", keywords, &type)) ++ return (NULL); ++ ++ if ((rval = dlpi_get_physaddr(link->dlpihdl, type, addr, &addrlen)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("y#", addr, addrlen)); ++} ++ ++PyDoc_STRVAR(set_physaddr_doc, ++ "set_physaddr(address) -> None\n" ++ "\n" ++ "Sets the physical address of the link.\n" ++ "See dlpi_set_physaddr(3DLPI).\n" ++); ++static PyObject * ++link_set_physaddr(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ char *addr = NULL; ++ size_t addrlen = 0; ++ static char *keywords[] = {"address", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "y#", keywords, ++ &addr, &addrlen)) ++ return (NULL); ++ ++ if ((rval = dlpi_set_physaddr(link->dlpihdl, DL_CURR_PHYS_ADDR, ++ addr, addrlen)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(promiscon_doc, ++ "promiscon([level]) -> None\n" ++ "\n" ++ "Enables promiscuous mode for the link at levels:\n" ++ "PROMISC_PHYS Promiscuous mode at the physical level(default)\n" ++ "PROMISC_SAP Promiscuous mode at the SAP level\n" ++ "PROMISC_MULTI Promiscuous mode for all multicast addresses\n" ++ "\n" ++ "The level modifier (OR'd with level) is:\n" ++ "PROMISC_NOLOOP Do not loopback messages to the client\n" ++ "See dlpi_promiscon(3DLPI).\n" ++); ++static PyObject * ++link_promiscon(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ uint_t level = DL_PROMISC_PHYS; ++ static char *keywords[] = {"level", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|I", keywords, &level)) ++ return (NULL); ++ ++ if ((rval = dlpi_promiscon(link->dlpihdl, level)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(promiscoff_doc, ++ "promiscoff([level]) -> None\n" ++ "\n" ++ "Disables promiscuous mode for the link at levels:\n" ++ "PROMISC_PHYS Promiscuous mode at the physical level(default)\n" ++ "PROMISC_SAP Promiscuous mode at the SAP level\n" ++ "PROMISC_MULTI Promiscuous mode for all multicast addresses\n" ++ "\n" ++ "The level modifier (OR'd with level) is:\n" ++ "PROMISC_NOLOOP Do not loopback messages to the client\n" ++ "See dlpi_promiscoff(3DLPI).\n" ++); ++static PyObject * ++link_promiscoff(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ uint_t level = DL_PROMISC_PHYS; ++ static char *keywords[] = {"level", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|I", keywords, &level)) ++ return (NULL); ++ ++ if ((rval = dlpi_promiscoff(link->dlpihdl, level)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(get_timeout_doc, ++ "get_timeout() -> int\n" ++ "\n" ++ "Returns current time out value of the link.\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_timeout(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("i", info.di_timeout)); ++} ++ ++PyDoc_STRVAR(get_mactype_doc, ++ "get_mactype() -> unsigned char\n" ++ "\n" ++ "Returns MAC type of the link.\n" ++ "See for the list of possible MAC types.\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_mactype(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("B", info.di_mactype)); ++} ++ ++PyDoc_STRVAR(set_timeout_doc, ++ "set_timeout(timeout) -> None\n" ++ "\n" ++ "Sets time out value of the link (default value: DEF_TIMEOUT).\n" ++ "See dlpi_set_timeout(3DLPI).\n" ++); ++static PyObject * ++link_set_timeout(pylink_t *link, PyObject *args, PyObject *kwds) ++{ ++ int timeout = DLPI_DEF_TIMEOUT; ++ static char *keywords[] = {"timeout", NULL}; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "i", keywords, &timeout)) ++ return (NULL); ++ ++ if ((rval = dlpi_set_timeout(link->dlpihdl, timeout)) != DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ Py_INCREF(Py_None); ++ return (Py_None); ++} ++ ++PyDoc_STRVAR(get_sdu_doc, ++ "get_sdu() -> (unsigned int, unsigned int)\n" ++ "\n" ++ "Returns (min sdu, max sdu).\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_sdu(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("II", info.di_min_sdu, info.di_max_sdu)); ++} ++ ++PyDoc_STRVAR(get_state_doc, ++ "get_state() -> unsigned int\n" ++ "\n" ++ "Returns current state of the link (either UNBOUND or IDLE).\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_state(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("I", info.di_state)); ++} ++ ++PyDoc_STRVAR(get_qos_select_doc, ++ "get_qos_select() -> (unsigned int, int, int, int)\n" ++ "\n" ++ "Returns (qos type, trans delay, priority, residul err).\n" ++ "Unsupported QOS parameters are set to UNKNOWN.\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_qos_select(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("Iiiii", ++ info.di_qos_sel.dl_qos_type, ++ info.di_qos_sel.dl_trans_delay, ++ info.di_qos_sel.dl_priority, ++ info.di_qos_sel.dl_residual_error)); ++} ++ ++PyDoc_STRVAR(get_qos_range_doc, ++ "get_qos_range() -> \n" ++ " (unsigned int, (int, int), (int, int), (int, int), int)\n" ++ "\n" ++ "Returns (qos type, (trans delay target, trans delay accept),\n" ++ "(min priority, max priority), (min protection, max protection),\n" ++ "residual err).\n" ++ "Unsupported QOS range values are set to UNKNOWN.\n" ++ "See dlpi_info(3DLPI).\n" ++); ++static PyObject * ++link_get_qos_range(pylink_t *link) ++{ ++ dlpi_info_t info; ++ int rval; ++ ++ if (link->dlpihdl == NULL) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ if ((rval = dlpi_info(link->dlpihdl, &info, 0)) != ++ DLPI_SUCCESS) { ++ dlpi_raise_exception(rval); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("I(ii)(ii)(ii)i", ++ info.di_qos_range.dl_qos_type, ++ info.di_qos_range.dl_trans_delay.dl_target_value, ++ info.di_qos_range.dl_trans_delay.dl_accept_value, ++ info.di_qos_range.dl_priority.dl_min, ++ info.di_qos_range.dl_priority.dl_max, ++ info.di_qos_range.dl_protection.dl_min, ++ info.di_qos_range.dl_protection.dl_max, ++ info.di_qos_range.dl_residual_error)); ++} ++ ++static PyMethodDef pylink_methods[] = { ++ {"bind", (PyCFunction)(uintptr_t)link_bind, METH_VARARGS|METH_KEYWORDS, ++ bind_doc}, ++ {"unbind", (PyCFunction)(uintptr_t)link_unbind, METH_NOARGS, ++ unbind_doc}, ++ {"send", (PyCFunction)(uintptr_t)link_send, METH_VARARGS|METH_KEYWORDS, ++ send_doc}, ++ {"recv", (PyCFunction)(uintptr_t)link_recv, METH_VARARGS|METH_KEYWORDS, ++ recv_doc}, ++ {"disabmulti", (PyCFunction)(uintptr_t)link_disabmulti, ++ METH_VARARGS|METH_KEYWORDS, disabmulti_doc}, ++ {"enabmulti", (PyCFunction)(uintptr_t)link_enabmulti, ++ METH_VARARGS|METH_KEYWORDS, enabmulti_doc}, ++ {"enabnotify", (PyCFunction)(uintptr_t)link_enabnotify, ++ METH_VARARGS|METH_KEYWORDS, enabnotify_doc}, ++ {"disabnotify", (PyCFunction)(uintptr_t)link_disabnotify, ++ METH_VARARGS|METH_KEYWORDS, disabnotify_doc}, ++ {"get_fd", (PyCFunction)(uintptr_t)link_get_fd, METH_NOARGS, ++ get_fd_doc}, ++ {"get_sap", (PyCFunction)(uintptr_t)link_get_sap, METH_NOARGS, ++ get_sap_doc}, ++ {"get_mactype", (PyCFunction)(uintptr_t)link_get_mactype, METH_NOARGS, ++ get_mactype_doc}, ++ {"get_linkname", (PyCFunction)(uintptr_t)link_get_linkname, ++ METH_NOARGS, get_linkname_doc}, ++ {"get_bcastaddr", (PyCFunction)(uintptr_t)link_get_bcastaddr, ++ METH_NOARGS, get_bcastaddr_doc}, ++ {"get_physaddr", (PyCFunction)(uintptr_t)link_get_physaddr, ++ METH_VARARGS|METH_KEYWORDS, get_physaddr_doc}, ++ {"set_physaddr", (PyCFunction)(uintptr_t)link_set_physaddr, ++ METH_VARARGS|METH_KEYWORDS, set_physaddr_doc}, ++ {"promiscon", (PyCFunction)(uintptr_t)link_promiscon, ++ METH_VARARGS|METH_KEYWORDS, promiscon_doc}, ++ {"promiscoff", (PyCFunction)(uintptr_t)link_promiscoff, ++ METH_VARARGS|METH_KEYWORDS, promiscoff_doc}, ++ {"get_timeout", (PyCFunction)(uintptr_t)link_get_timeout, ++ METH_NOARGS, get_timeout_doc}, ++ {"set_timeout", (PyCFunction)(uintptr_t)link_set_timeout, ++ METH_VARARGS|METH_KEYWORDS, set_timeout_doc}, ++ {"get_sdu", (PyCFunction)(uintptr_t)link_get_sdu, METH_NOARGS, ++ get_sdu_doc}, ++ {"get_state", (PyCFunction)(uintptr_t)link_get_state, METH_NOARGS, ++ get_state_doc}, ++ {"get_qos_select", (PyCFunction)(uintptr_t)link_get_qos_select, ++ METH_NOARGS, get_qos_select_doc}, ++ {"get_qos_range", (PyCFunction)(uintptr_t)link_get_qos_range, ++ METH_NOARGS, get_qos_range_doc}, ++ {NULL, NULL} ++}; ++ ++static PyTypeObject pylink_type = { ++ PyVarObject_HEAD_INIT(NULL, 0) /* Must fill in type value later */ ++ "dlpi.link", /* tp_name */ ++ sizeof(pylink_t), /* tp_basicsize */ ++ 0, /* tp_itemsize */ ++ (destructor)link_dealloc, /* tp_dealloc */ ++ 0, /* tp_print */ ++ 0, /* tp_getattr */ ++ 0, /* tp_setattr */ ++ 0, /* tp_reserved */ ++ 0, /* tp_repr */ ++ 0, /* tp_as_number */ ++ 0, /* tp_as_sequence */ ++ 0, /* tp_as_mapping */ ++ 0, /* tp_hash */ ++ 0, /* tp_call */ ++ 0, /* tp_str */ ++ 0, /* tp_getattro */ ++ 0, /* tp_setattro */ ++ 0, /* tp_as_buffer */ ++ Py_TPFLAGS_DEFAULT, /* tp_flags */ ++ link_doc, /* tp_doc */ ++ 0, /* tp_traverse */ ++ 0, /* tp_clear */ ++ 0, /* tp_richcompare */ ++ 0, /* tp_weaklistoffset */ ++ 0, /* tp_iter */ ++ 0, /* tp_iternext */ ++ pylink_methods, /* tp_methods */ ++ 0, /* tp_members */ ++ 0, /* tp_getset */ ++ 0, /* tp_base */ ++ 0, /* tp_dict */ ++ 0, /* tp_descr_get */ ++ 0, /* tp_descr_set */ ++ 0, /* tp_dictoffset */ ++ (initproc)link_init, /* tp_init */ ++ 0, /* tp_alloc */ ++ PyType_GenericNew, /* tp_new */ ++ 0, /* tp_free */ ++ 0, /* tp_is_gc */ ++}; ++ ++PyDoc_STRVAR(arptype_doc, ++ "arptype(arptype) -> unsigned int\n" ++ "\n" ++ "Converts a DLPI MAC type to an ARP hardware type defined\n" ++ " in \n" ++ "See dlpi_arptype(3DLPI)\n" ++); ++static PyObject * ++arptype(PyObject *self, PyObject *args, PyObject *kwds) ++{ ++ static char *keywords[] = {"arptype", NULL}; ++ uint_t dlpityp, arptyp; ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I", keywords, &dlpityp)) ++ return (NULL); ++ ++ if ((arptyp = dlpi_arptype(dlpityp)) == 0) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("I", arptyp)); ++} ++ ++PyDoc_STRVAR(iftype_doc, ++ "iftype(iftype) -> unsigned int\n" ++ "\n" ++ "Converts a DLPI MAC type to a BSD socket interface type\n" ++ "defined in \n" ++ "See dlpi_iftype(3DLPI)\n" ++); ++static PyObject * ++iftype(PyObject *self, PyObject *args, PyObject *kwds) ++{ ++ static char *keywords[] = {"iftype", NULL}; ++ uint_t dlpityp, iftyp; ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I", keywords, &dlpityp)) ++ return (NULL); ++ ++ if ((iftyp = dlpi_iftype(dlpityp)) == 0) { ++ errno = EINVAL; ++ dlpi_raise_exception(DL_SYSERR); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("I", iftyp)); ++} ++ ++PyDoc_STRVAR(mactype_doc, ++ "mactype(mactype) -> string\n" ++ "\n" ++ "Returns a string that describes the specified mactype.\n" ++ "Valid mac types are defined in .\n" ++ "See dlpi_mactype(3DLPI)\n" ++); ++static PyObject * ++mactype(PyObject *self, PyObject *args, PyObject *kwds) ++{ ++ static char *keywords[] = {"mactype", NULL}; ++ uint_t mactyp; ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwds, "I", keywords, &mactyp)) ++ return (NULL); ++ ++ return (Py_BuildValue("s", dlpi_mactype(mactyp))); ++} ++ ++static boolean_t ++link_walker(const char *name, void *arg) ++{ ++ PyObject *linkname; ++ PyObject *list = (PyObject *)arg; ++ ++ if ((list == NULL) || !PyList_Check(list)) ++ return (B_FALSE); ++ ++ linkname = Py_BuildValue("s", name); ++ if (PyList_Append(list, linkname) == -1) ++ return (B_TRUE); ++ ++ Py_DECREF(linkname); ++ return (B_FALSE); ++} ++ ++PyDoc_STRVAR(listlink_doc, ++ "listlink() -> list\n" ++ "\n" ++ "Returns a list containing link names of all links on the system.\n" ++); ++static PyObject * ++listlink(PyObject *self, PyObject *args) ++{ ++ PyObject *list; ++ ++ if ((list = PyList_New(0)) == NULL) ++ return (NULL); ++ ++ dlpi_walk(link_walker, list, 0); ++ return (list); ++} ++ ++static PyMethodDef dlpi_methods[] = { ++ {"arptype", (PyCFunction)(uintptr_t)arptype, METH_VARARGS|METH_KEYWORDS, ++ arptype_doc}, ++ {"iftype", (PyCFunction)(uintptr_t)iftype, METH_VARARGS|METH_KEYWORDS, ++ iftype_doc}, ++ {"mactype", (PyCFunction)(uintptr_t)mactype, METH_VARARGS|METH_KEYWORDS, ++ mactype_doc}, ++ {"listlink", listlink, METH_NOARGS, listlink_doc}, ++ {NULL} ++}; ++ ++PyMODINIT_FUNC ++PyInit_dlpi (void) ++{ ++ PyObject *mod; ++ ++ if (PyType_Ready(&pylink_type) < 0) ++ return NULL; ++ ++ static struct PyModuleDef moduledef = { ++ PyModuleDef_HEAD_INIT, ++ "dlpi", ++ NULL, ++ -1, ++ dlpi_methods, ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++ }; ++ ++ mod = PyModule_Create(&moduledef); ++ if (mod == NULL) ++ return NULL; ++ ++ dlpi_err = PyErr_NewException("dlpi.error", NULL, NULL); ++ if (dlpi_err == NULL) ++ return NULL; ++ PyModule_AddObject(mod, "error", dlpi_err); ++ ++ Py_INCREF(&pylink_type); ++ PyModule_AddObject(mod, "link", (PyObject *)&pylink_type); ++ PyModule_AddIntConstant(mod, "PASSIVE", DLPI_PASSIVE); ++ PyModule_AddIntConstant(mod, "RAW", DLPI_RAW); ++ PyModule_AddIntConstant(mod, "NATIVE", DLPI_NATIVE); ++ PyModule_AddIntConstant(mod, "ANY_SAP", DLPI_ANY_SAP); ++ PyModule_AddIntConstant(mod, "DEF_TIMEOUT", DLPI_DEF_TIMEOUT); ++ PyModule_AddIntConstant(mod, "NOTE_LINK_DOWN", DL_NOTE_LINK_DOWN); ++ PyModule_AddIntConstant(mod, "NOTE_LINK_UP", DL_NOTE_LINK_UP); ++ PyModule_AddIntConstant(mod, "NOTE_PHYS_ADDR", DL_NOTE_PHYS_ADDR); ++ PyModule_AddIntConstant(mod, "NOTE_SDU_SIZE", DL_NOTE_SDU_SIZE); ++ PyModule_AddIntConstant(mod, "NOTE_SPEED", DL_NOTE_SPEED); ++ PyModule_AddIntConstant(mod, "NOTE_PROMISC_ON_PHYS", ++ DL_NOTE_PROMISC_ON_PHYS); ++ PyModule_AddIntConstant(mod, "NOTE_PROMISC_OFF_PHYS", ++ DL_NOTE_PROMISC_OFF_PHYS); ++ PyModule_AddIntConstant(mod, "FACT_PHYS_ADDR", DL_FACT_PHYS_ADDR); ++ PyModule_AddIntConstant(mod, "CURR_PHYS_ADDR", DL_CURR_PHYS_ADDR); ++ PyModule_AddIntConstant(mod, "PROMISC_PHYS", DL_PROMISC_PHYS); ++ PyModule_AddIntConstant(mod, "PROMISC_SAP", DL_PROMISC_SAP); ++ PyModule_AddIntConstant(mod, "PROMISC_MULTI", DL_PROMISC_MULTI); ++ //PyModule_AddIntConstant(mod, "PROMISC_NOLOOP", DL_PROMISC_NOLOOP); ++ PyModule_AddIntConstant(mod, "UNKNOWN", DL_UNKNOWN); ++ PyModule_AddIntConstant(mod, "UNBOUND", DL_UNBOUND); ++ PyModule_AddIntConstant(mod, "IDLE", DL_IDLE); ++ PyModule_AddIntConstant(mod, "SYSERR", DL_SYSERR); ++ ++ return mod; ++} diff --git a/build/python313/patches/module-priv-rbac.patch b/build/python313/patches/module-priv-rbac.patch new file mode 100644 index 0000000000..18048f7024 --- /dev/null +++ b/build/python313/patches/module-priv-rbac.patch @@ -0,0 +1,1291 @@ +This patch provides Python RBAC support. It may be contributed upstream at +some point, but the suitability (or lack thereof) has not yet been determined. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/Setup.local a/Modules/Setup.local +--- a~/Modules/Setup.local 1970-01-01 00:00:00 ++++ a/Modules/Setup.local 1970-01-01 00:00:00 +@@ -2,3 +2,5 @@ + + ucred ucred.c -ltsol + dlpi dlpimodule.c -ldlpi ++privileges privileges.c ++rbac pyrbac.c authattr.c execattr.c userattr.c -lnsl -lsocket -lsecdb +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/authattr.c a/Modules/authattr.c +--- a~/Modules/authattr.c 1970-01-01 00:00:00 ++++ a/Modules/authattr.c 1970-01-01 00:00:00 +@@ -0,0 +1,254 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++ ++/* ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ */ ++ ++/* ++ * RBAC Bindings for Python - auth_attr functions ++ */ ++ ++#include ++#include "Python.h" ++#include "pyrbac.h" ++ ++static PyObject* ++pyrbac_setauthattr(PyObject* self, PyObject* args) { ++ setauthattr(); ++ return Py_None; ++} ++ ++static PyObject* ++pyrbac_endauthattr(PyObject* self, PyObject* args) { ++ endauthattr(); ++ return Py_None; ++} ++ ++PyObject* ++pyrbac_getauthnamattr(PyObject* self, char* authname, int mode) { ++ ++ ++ ++ authattr_t * ret_authattr = (mode == PYRBAC_NAM_MODE) ? getauthnam(authname) : getauthattr(); ++ if (ret_authattr == NULL) ++ return Py_None; ++ ++ PyObject* kv_data = PyDict_New(); ++ if (kv_data == NULL) { ++ free_authattr(ret_authattr); ++ return NULL; ++ } ++ ++ if(ret_authattr->attr != NULL) { ++ int len; ++ for(len = 0; len < ret_authattr->attr->length; len++) { ++ kv_t current = ret_authattr->attr->data[len]; ++ ++ PyObject* set = PyList_New(0); ++ char* saveptr; ++ char* item = strtok_r(current.value, ",", &saveptr); ++ PyList_Append(set, PyUnicode_FromString(item)); ++ ++ while((item = strtok_r(NULL, ",", &saveptr)) != NULL) { ++ if(PyList_Append(set, PyUnicode_FromString(item)) != 0) { ++ Py_XDECREF(set); ++ Py_XDECREF(kv_data); ++ free_authattr(ret_authattr); ++ return NULL; ++ } ++ } ++ if(PyDict_SetItemString(kv_data, current.key, set)) { ++ free_authattr(ret_authattr); ++ return NULL; ++ } ++ } ++ } ++ PyObject * retval = Py_BuildValue("{s:s,s:s,s:s,s:s,s:s,s:O}", ++ "name",ret_authattr->name, ++ "res1",ret_authattr->res1, ++ "res2",ret_authattr->res2, ++ "short",ret_authattr->short_desc, ++ "long",ret_authattr->long_desc, ++ "attributes",kv_data); ++ ++ free_authattr(ret_authattr); ++ return retval; ++ ++} ++ ++static PyObject* ++pyrbac_getauthattr(PyObject* self, PyObject* args) { ++ return(pyrbac_getauthnamattr(self, NULL, PYRBAC_ATTR_MODE)); ++} ++ ++static PyObject* ++pyrbac_getauthnam(PyObject* self, PyObject* args) { ++ char* name = NULL; ++ if(!PyArg_ParseTuple(args, "s:getauthnam", &name)) ++ return NULL; ++ return(pyrbac_getauthnamattr(self, name, PYRBAC_NAM_MODE)); ++} ++ ++static PyObject * ++pyrbac_chkauthattr(PyObject* self, PyObject* args) { ++ char* authstring = NULL; ++ char* username = NULL; ++ if(!PyArg_ParseTuple(args, "ss:chkauthattr", &authstring, &username)) ++ return NULL; ++ return PyBool_FromLong((long)chkauthattr(authstring, username)); ++} ++ ++static PyObject* ++pyrbac_authattr_next(PyObject* self) { ++ PyObject* retval = pyrbac_getauthattr(self, NULL); ++ if( retval == Py_None ) { ++ setauthattr(); ++ return NULL; ++ } ++ return retval; ++} ++static PyObject* ++pyrbac_authattr__iter__(PyObject* self) { ++ return self; ++} ++ ++typedef struct { ++ PyObject_HEAD ++} Authattr; ++ ++static void ++Authattr_dealloc(Authattr* self) { ++ endauthattr(); ++ Py_TYPE(self)->tp_free((PyObject*) self); ++} ++ ++static PyObject* ++Authattr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { ++ Authattr *self; ++ self = (Authattr*)type->tp_alloc(type, 0); ++ ++ return ((PyObject *) self); ++} ++ ++static int ++Authattr_init(Authattr* self, PyObject *args, PyObject *kwargs) { ++ setauthattr(); ++ return 0; ++} ++ ++PyDoc_STRVAR(pyrbac_authattr__doc__, """provides interfaces to the auth_attr \ ++database. may be iterated over to return all auth_attr entries\n\n\ ++Methods provided:\n\ ++setauthattr\n\ ++endauthattr\n\ ++getauthattr\n\ ++chkauthattr\n\ ++getauthnam"""); ++ ++PyDoc_STRVAR(pyrbac_setauthattr__doc__, ++"\"rewinds\" the auth_attr functions to the first entry in the db. Called \ ++automatically by the constructor\n\tArguments: None\n\tReturns: None"); ++ ++PyDoc_STRVAR(pyrbac_endauthattr__doc__, ++"closes the auth_attr database, cleans up storage. called automatically by \ ++the destructor\n\tArguments: None\n\tReturns: None"); ++ ++PyDoc_STRVAR(pyrbac_chkauthattr__doc__, "verifies if a user has a given \ ++authorization.\n\tArguments: 2 Python strings, 'authname' and 'username'\n\ ++\tReturns: True if the user is authorized, False otherwise"); ++ ++PyDoc_STRVAR(pyrbac_getauthattr__doc__, ++"return one entry from the auth_attr database\n\ ++\tArguments: None\n\ ++\tReturns: a dict representing the authattr_t struct:\n\ ++\t\t\"name\": Authorization Name\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"short\": Short Description\n\ ++\t\t\"long\": Long Description\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as either a list \ ++or a string depending on value"); ++ ++PyDoc_STRVAR(pyrbac_getauthnam__doc__, ++"searches the auth_attr database for a given authorization name\n\ ++\tArguments: a Python string containing the auth name\n\ ++\tReturns: a dict representing the authattr_t struct:\n\ ++\t\t\"name\": Authorization Name\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"short\": Short Description\n\ ++\t\t\"long\": Long Description\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as either a list \ ++or a string depending on value"); ++ ++static PyMethodDef Authattr_methods[] = { ++ {"setauthattr", pyrbac_setauthattr, METH_NOARGS, pyrbac_setauthattr__doc__}, ++ {"endauthattr", pyrbac_endauthattr, METH_NOARGS, pyrbac_endauthattr__doc__}, ++ {"chkauthattr", pyrbac_chkauthattr, METH_VARARGS, pyrbac_chkauthattr__doc__}, ++ {"getauthattr", pyrbac_getauthattr, METH_NOARGS, pyrbac_getauthattr__doc__}, ++ {"getauthnam", pyrbac_getauthnam, METH_VARARGS, pyrbac_getauthnam__doc__}, ++ {NULL, NULL} ++}; ++ ++PyTypeObject AuthattrType = { ++ PyVarObject_HEAD_INIT(NULL, 0) ++ "rbac.authattr", /*tp_name*/ ++ sizeof(Authattr), /*tp_basicsize*/ ++ 0, /*tp_itemsize*/ ++ (destructor)Authattr_dealloc, /*tp_dealloc*/ ++ 0, /*tp_print*/ ++ 0, /*tp_getattr*/ ++ 0, /*tp_setattr*/ ++ 0, /*tp_reserved*/ ++ 0, /*tp_repr*/ ++ 0, /*tp_as_number*/ ++ 0, /*tp_as_sequence*/ ++ 0, /*tp_as_mapping*/ ++ 0, /*tp_hash */ ++ 0, /*tp_call*/ ++ 0, /*tp_str*/ ++ 0, /*tp_getattro*/ ++ 0, /*tp_setattro*/ ++ 0, /*tp_as_buffer*/ ++ Py_TPFLAGS_DEFAULT | ++ Py_TPFLAGS_BASETYPE, /*tp_flags*/ ++ pyrbac_authattr__doc__, /* tp_doc */ ++ 0, /* tp_traverse */ ++ 0, /* tp_clear */ ++ 0, /* tp_richcompare */ ++ 0, /* tp_weaklistoffset */ ++ pyrbac_authattr__iter__, /* tp_iter */ ++ pyrbac_authattr_next, /* tp_iternext */ ++ Authattr_methods, /* tp_methods */ ++ 0, /* tp_members */ ++ 0, /* tp_getset */ ++ 0, /* tp_base */ ++ 0, /* tp_dict */ ++ 0, /* tp_descr_get */ ++ 0, /* tp_descr_set */ ++ 0, /* tp_dictoffset */ ++ (initproc)Authattr_init, /* tp_init */ ++ 0, /* tp_alloc */ ++ Authattr_new, /* tp_new */ ++ 0, /* tp_free */ ++ 0, /* tp_is_gc */ ++}; +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/execattr.c a/Modules/execattr.c +--- a~/Modules/execattr.c 1970-01-01 00:00:00 ++++ a/Modules/execattr.c 1970-01-01 00:00:00 +@@ -0,0 +1,305 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++ ++/* ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ */ ++ ++/* ++ * RBAC Bindings for Python - exec_attr functions ++ */ ++ ++#include ++#include "Python.h" ++#include "pyrbac.h" ++ ++static PyObject * ++pyrbac_setexecattr(PyObject* self, PyObject* args) { ++ setexecattr(); ++ return Py_None; ++} ++ ++static PyObject * ++pyrbac_endexecattr(PyObject* self, PyObject* args) { ++ endexecattr(); ++ return Py_None; ++} ++ ++PyObject * ++pyrbac_getexecuserprofattr(PyObject* self, char* userprofname, char* type, char* id, int mode) { ++ ++ PyObject* ep_data = (mode == PYRBAC_ATTR_MODE) ? NULL : PyList_New(0); ++ ++ if (ep_data == NULL && mode != PYRBAC_ATTR_MODE ) ++ return NULL; ++ ++ execattr_t *execprof; ++ if (mode == PYRBAC_USER_MODE) ++ execprof = getexecuser(userprofname, type, id, GET_ALL); ++ else if (mode == PYRBAC_PROF_MODE) ++ execprof = getexecprof(userprofname, type, id, GET_ALL); ++ else if (mode == PYRBAC_ATTR_MODE) ++ execprof = getexecattr(); ++ else ++ return NULL; ++ ++ if (execprof == NULL) ++ return Py_None; ++ ++ execattr_t *execprof_head = execprof; ++ ++ while(execprof != NULL) { ++ ++ PyObject* kv_data = PyDict_New(); ++ ++ if(execprof->attr != NULL) { ++ int len; ++ for(len = 0; len < execprof->attr->length; len++) { ++ kv_t current = execprof->attr->data[len]; ++ ++ PyObject* set = PyList_New(0); ++ char* saveptr; ++ char* item = strtok_r(current.value, ",", &saveptr); ++ PyList_Append(set, PyUnicode_FromString(item)); ++ ++ while((item = strtok_r(NULL, ",", &saveptr)) != NULL) { ++ if(PyList_Append(set, PyUnicode_FromString(item)) != 0) { ++ Py_XDECREF(set); ++ Py_XDECREF(kv_data); ++ free_execattr(execprof_head); ++ return NULL; ++ } ++ } ++ if(PyDict_SetItemString(kv_data, current.key, set)) { ++ free_execattr(execprof_head); ++ return NULL; ++ } ++ } ++ } ++ PyObject* entry = Py_BuildValue("{s:s,s:s,s:s,s:s,s:s,s:s,s:O}", ++ "name", execprof->name, ++ "type", execprof->type, ++ "policy", execprof->policy, ++ "res1", execprof->res1, ++ "res2", execprof->res2, ++ "id", execprof->id, ++ "attributes", kv_data); ++ ++ if (entry == NULL) { ++ Py_XDECREF(kv_data); ++ free_execattr(execprof_head); ++ return NULL; ++ } ++ ++ if (mode == PYRBAC_ATTR_MODE) { ++ free_execattr(execprof_head); ++ return(entry); ++ } ++ PyList_Append(ep_data, entry); ++ execprof = execprof->next; ++ } ++ ++ free_execattr(execprof_head); ++ return(ep_data); ++ ++} ++ ++static PyObject * ++pyrbac_getexecuser(PyObject* self, PyObject* args) { ++ char* username = NULL; ++ char* type = NULL; ++ char* id = NULL; ++ ++ if(!PyArg_ParseTuple(args, "sss:getexecuser", &username, &type, &id)) ++ return NULL; ++ ++ return (pyrbac_getexecuserprofattr(self, username, type, id, PYRBAC_USER_MODE)); ++} ++ ++static PyObject * ++pyrbac_getexecprof(PyObject* self, PyObject* args) { ++ ++ char* profname = NULL; ++ char* type = NULL; ++ char* id = NULL; ++ ++ if(!PyArg_ParseTuple(args, "sss:getexecprof", &profname, &type, &id)) ++ return NULL; ++ ++ return (pyrbac_getexecuserprofattr(self, profname, type, id, PYRBAC_PROF_MODE)); ++} ++ ++static PyObject* ++pyrbac_getexecattr(PyObject* self, PyObject* args) { ++ return pyrbac_getexecuserprofattr(self, NULL, NULL, NULL, PYRBAC_ATTR_MODE); ++} ++ ++static PyObject* ++pyrbac_execattr_next(PyObject* self) { ++ PyObject* retval = pyrbac_getexecattr(self, NULL); ++ if( retval == Py_None ) { ++ setexecattr(); ++ return NULL; ++ } ++ return retval; ++} ++static PyObject* ++pyrbac_execattr__iter__(PyObject* self) { ++ return self; ++} ++ ++typedef struct { ++ PyObject_HEAD ++} Execattr; ++ ++static void ++Execattr_dealloc(Execattr* self) { ++ endexecattr(); ++ Py_TYPE(self)->tp_free((PyObject*) self); ++} ++ ++static PyObject* ++Execattr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { ++ Execattr *self; ++ self = (Execattr*)type->tp_alloc(type, 0); ++ ++ return ((PyObject *) self); ++} ++ ++static int ++Execattr_init(Execattr* self, PyObject *args, PyObject *kwargs) { ++ setexecattr(); ++ return 0; ++} ++ ++PyDoc_STRVAR(pyrbac_execattr__doc__, "provides functions for \ ++interacting with the execution profiles database. May be iterated over to \ ++enumerate exec_attr(5) entries\n\n\ ++Methods provided:\n\ ++setexecattr\n\ ++endexecattr\n\ ++getexecattr\n\ ++getexecprof\n\ ++getexecuser"); ++ ++ ++PyDoc_STRVAR(pyrbac_setexecattr__doc__, ++"\"rewinds\" the exec_attr functions to the first entry in the db. Called \ ++automatically by the constructor.\n\ ++\tArguments: None\ ++\tReturns: None"); ++ ++PyDoc_STRVAR(pyrbac_endexecattr__doc__, ++"closes the exec_attr database, cleans up storage. called automatically by \ ++the destructor.\n\ ++\tArguments: None\ ++\tReturns: None"); ++ ++PyDoc_STRVAR(pyrbac_getexecuser__doc__, "corresponds with getexecuser(3SECDB)\ ++\nTakes: \'username\', \'type\', \'id\'\n\ ++Return: a single exec_attr entry\n\ ++\tArguments: None\n\ ++\tReturns: a dict representation of an execattr_t struct:\n\ ++\t\t\"name\": Authorization Name\n\ ++\t\t\"type\": Profile Type\n\ ++\t\t\"policy\": Policy attributes are relevant in\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"id\": unique identifier\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as\ ++either a list or a string depending on value"); ++ ++PyDoc_STRVAR(pyrbac_getexecprof__doc__, "corresponds with getexecprof(3SECDB)\ ++\nTakes: \'profile name\', \'type\', \'id\'\n\ ++\tReturns: a dict representation of an execattr_t struct:\n\ ++\t\t\"name\": Authorization Name\n\ ++\t\t\"type\": Profile Type\n\ ++\t\t\"policy\": Policy attributes are relevant in\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"id\": unique identifier\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as\ ++either a list or a string depending on value"); ++ ++PyDoc_STRVAR(pyrbac_getexecattr__doc__, "corresponds with getexecattr(3SECDB)\ ++\nTakes 0 arguments\n\ ++\tReturns: a dict representation of an execattr_t struct:\n\ ++\t\t\"name\": Authorization Name\n\ ++\t\t\"type\": Profile Type\n\ ++\t\t\"policy\": Policy attributes are relevant in\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"id\": unique identifier\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as\ ++either a list or a string depending on value"); ++ ++static PyMethodDef Execattr_methods[] = { ++ {"setexecattr", pyrbac_setexecattr, METH_NOARGS, pyrbac_setexecattr__doc__}, ++ {"endexecattr", pyrbac_endexecattr, METH_NOARGS, pyrbac_endexecattr__doc__}, ++ {"getexecprof", pyrbac_getexecprof, METH_VARARGS, pyrbac_getexecprof__doc__}, ++ {"getexecuser", pyrbac_getexecuser, METH_VARARGS, pyrbac_getexecuser__doc__}, ++ {"getexecattr", pyrbac_getexecattr, METH_NOARGS, pyrbac_getexecattr__doc__}, ++ {NULL, NULL} ++}; ++ ++PyTypeObject ExecattrType = { ++ PyVarObject_HEAD_INIT(NULL, 0) ++ "rbac.execattr", /*tp_name*/ ++ sizeof(Execattr), /*tp_basicsize*/ ++ 0, /*tp_itemsize*/ ++ (destructor)Execattr_dealloc, /*tp_dealloc*/ ++ 0, /*tp_print*/ ++ 0, /*tp_getattr*/ ++ 0, /*tp_setattr*/ ++ 0, /*tp_reserved*/ ++ 0, /*tp_repr*/ ++ 0, /*tp_as_number*/ ++ 0, /*tp_as_sequence*/ ++ 0, /*tp_as_mapping*/ ++ 0, /*tp_hash */ ++ 0, /*tp_call*/ ++ 0, /*tp_str*/ ++ 0, /*tp_getattro*/ ++ 0, /*tp_setattro*/ ++ 0, /*tp_as_buffer*/ ++ Py_TPFLAGS_DEFAULT | ++ Py_TPFLAGS_BASETYPE, /*tp_flags*/ ++ pyrbac_execattr__doc__, /* tp_doc */ ++ 0, /* tp_traverse */ ++ 0, /* tp_clear */ ++ 0, /* tp_richcompare */ ++ 0, /* tp_weaklistoffset */ ++ pyrbac_execattr__iter__, /* tp_iter */ ++ pyrbac_execattr_next, /* tp_iternext */ ++ Execattr_methods, /* tp_methods */ ++ 0, /* tp_members */ ++ 0, /* tp_getset */ ++ 0, /* tp_base */ ++ 0, /* tp_dict */ ++ 0, /* tp_descr_get */ ++ 0, /* tp_descr_set */ ++ 0, /* tp_dictoffset */ ++ (initproc)Execattr_init, /* tp_init */ ++ 0, /* tp_alloc */ ++ Execattr_new, /* tp_new */ ++ 0, /* tp_free */ ++ 0, /* tp_is_gc */ ++}; +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/privileges.c a/Modules/privileges.c +--- a~/Modules/privileges.c 1970-01-01 00:00:00 ++++ a/Modules/privileges.c 1970-01-01 00:00:00 +@@ -0,0 +1,269 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++ ++/* ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. ++ */ ++ ++/* ++ * privileges(7) bindings for Python ++ */ ++ ++#include ++#include "Python.h" ++ ++/* ++ * In userspace, privilege types are represented as strings. ++ * In the python library, numbers are used. This is the translation table. ++ */ ++struct xlate { ++ int cmd; ++ priv_ptype_t op; ++ const char *name; ++} xlates[] = { ++ { 0, PRIV_EFFECTIVE, "PRIV_EFFECTIVE" }, ++ { 1, PRIV_INHERITABLE, "PRIV_INHERITABLE" }, ++ { 2, PRIV_PERMITTED, "PRIV_PERMITTED" }, ++ { 3, PRIV_LIMIT, "PRIV_LIMIT" }, ++ { 0, NULL, NULL } ++}; ++ ++static PyObject * ++pyprivileges_setppriv( PyObject *self, PyObject *args) { ++ priv_op_t op = -1 ; ++ priv_ptype_t which = NULL; ++ int cmd, i; ++ ++ PyObject* set_list = NULL; ++ ++ priv_set_t * set = NULL; ++ ++ if(!PyArg_ParseTuple(args, "iiO:setppriv", &op, &cmd, &set_list)) ++ return NULL; ++ ++ for (i = 0; xlates[i].op != NULL; i++) { ++ if (xlates[i].cmd == cmd) { ++ which = xlates[i].op; ++ break; ++ } ++ } ++ ++ if (op != PRIV_ON && op != PRIV_OFF && op != PRIV_SET) ++ return NULL; ++ if (which == NULL) ++ return NULL; ++ ++ PyObject *set_string = PyList_GetItem(set_list, 0); ++ for (i = 1; i < PyList_Size(set_list); ++i) { ++ PyUnicode_Concat(set_string, PyUnicode_FromString(",")); ++ PyUnicode_Concat(set_string, PyList_GetItem(set_list, i)); ++ } ++ ++ set = priv_str_to_set( ++ PyBytes_AsString(PyUnicode_AsUTF8String(set_string)), ",", NULL ); ++ ++ if (set == NULL) ++ return NULL; ++ ++ long ret = (long) setppriv(op, which, set); ++ priv_freeset(set); ++ // Python inverts true & false ++ if(ret) ++ Py_RETURN_FALSE; ++ ++ Py_RETURN_TRUE; ++} ++ ++static PyObject * ++pyprivileges_getppriv( PyObject *self, PyObject *args) { ++ ++ char* set_str = NULL; ++ priv_ptype_t which = NULL; ++ priv_set_t * set = priv_allocset(); ++ int i, cmd; ++ ++ if (set == NULL) ++ return NULL; ++ ++ if(!PyArg_ParseTuple(args, "i:getppriv", &cmd)) ++ return NULL; ++ ++ for (i = 0; xlates[i].op != NULL; i++) { ++ if (xlates[i].cmd == cmd) { ++ which = xlates[i].op; ++ break; ++ } ++ } ++ ++ if (which == NULL) ++ return NULL; ++ ++ if (getppriv(which, set) != 0) ++ return NULL; ++ ++ set_str = priv_set_to_str(set, ',', PRIV_STR_LIT); ++ priv_freeset(set); ++ ++ PyObject* set_list = PyList_New(0); ++ char* saveptr; ++ char* item = strtok_r(set_str, ",", &saveptr); ++ PyList_Append(set_list, PyUnicode_FromString(item)); ++ ++ while((item = strtok_r(NULL, ",", &saveptr)) != NULL) { ++ if(PyList_Append(set_list, PyUnicode_FromString(item)) != 0) { ++ Py_XDECREF(set_list); ++ return NULL; ++ } ++ } ++ ++ return(set_list); ++} ++ ++static PyObject * ++pyprivileges_priv_inverse( PyObject *self, PyObject *args ) { ++ ++ PyObject* set_list_in = NULL; ++ if(!PyArg_ParseTuple(args, "O:priv_inverse", &set_list_in)) ++ return NULL; ++ ++ PyObject* set_string = PyList_GetItem(set_list_in, 0); ++ int i; ++ for (i = 1; i < PyList_Size(set_list_in); ++i) { ++ PyBytes_Concat(&set_string, PyBytes_FromString(",")); ++ PyBytes_Concat(&set_string, PyList_GetItem(set_list_in, i)); ++ } ++ ++ priv_set_t * set = priv_str_to_set(PyBytes_AsString(set_string), ",", NULL); ++ if (set == NULL) ++ return NULL; ++ priv_inverse(set); ++ char * ret_str = priv_set_to_str(set, ',', PRIV_STR_LIT); ++ priv_freeset(set); ++ ++ PyObject* set_list_out = PyList_New(0); ++ char* saveptr; ++ char* item = strtok_r(ret_str, ",", &saveptr); ++ PyList_Append(set_list_out, PyBytes_FromString(item)); ++ ++ while((item = strtok_r(NULL, ",", &saveptr)) != NULL) { ++ if(PyList_Append(set_list_out, PyBytes_FromString(item)) != 0) { ++ Py_XDECREF(set_list_out); ++ return NULL; ++ } ++ } ++ ++ Py_XDECREF(set_list_in); ++ ++ return(set_list_out); ++} ++ ++/* priv_ineffect is a convienient wrapper to priv_get ++ * however priv_set is, in the context of python, not ++ * much of a convienience, so it's omitted ++ */ ++static PyObject * ++pyprivileges_priv_ineffect(PyObject* self, PyObject* args) { ++ char* privstring=NULL; ++ if (!PyArg_ParseTuple(args, "s:priv_ineffect", &privstring)) ++ return NULL; ++ return PyBool_FromLong(priv_ineffect(privstring)); ++} ++ ++PyDoc_STRVAR(pyprivileges__doc__, ++"Provides functions for interacting with the Solaris privileges(7) framework\n\ ++Functions provided:\n\ ++setppriv\n\ ++getppriv\n\ ++priv_ineffect\n\ ++priv_inverse"); ++ ++PyDoc_STRVAR(pyprivileges_setppriv__doc__, ++"Facilitates setting the permitted/inheritable/limit/effective privileges set\n\ ++\tArguments:\n\ ++\t\tone of (PRIV_ON|PRIV_OFF|PRIV_SET)\n\ ++\t\tone of (PRIV_PERMITTED|PRIV_INHERITABLE|PRIV_LIMIT|PRIV_EFFECTIVE)\n\ ++\t\tset of privileges: a list of strings\n\ ++\tReturns: True on success, False on failure\ ++"); ++ ++PyDoc_STRVAR(pyprivileges_getppriv__doc__, ++"Return the process privilege set\n\ ++\tArguments:\n\ ++\t\tone of (PRIV_PERMITTED|PRIV_INHERITABLE|PRIV_LIMIT|PRIV_EFFECTIVE)\n\ ++\tReturns: a Python list of strings"); ++ ++PyDoc_STRVAR(pyprivileges_priv_ineffect__doc__, ++"Checks for a privileges presence in the effective set\n\ ++\tArguments: a String\n\ ++\tReturns: True if the privilege is in effect, False otherwise"); ++ ++PyDoc_STRVAR(pyprivileges_priv_inverse__doc__, ++"The complement of the set of privileges\n\ ++\tArguments: a list of strings\n\tReturns: a list of strings"); ++ ++static PyMethodDef module_methods[] = { ++ {"setppriv", pyprivileges_setppriv, METH_VARARGS, pyprivileges_setppriv__doc__}, ++ {"getppriv", pyprivileges_getppriv, METH_VARARGS, pyprivileges_getppriv__doc__}, ++ {"priv_ineffect", pyprivileges_priv_ineffect, METH_VARARGS, pyprivileges_priv_ineffect__doc__}, ++ {"priv_inverse", pyprivileges_priv_inverse, METH_VARARGS, pyprivileges_priv_inverse__doc__}, ++ {NULL, NULL} ++}; ++ ++ ++#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ ++#define PyMODINIT_FUNC void ++#endif ++PyMODINIT_FUNC ++PyInit_privileges (void) { ++ PyObject* m; ++ ++ static struct PyModuleDef moduledef = { ++ PyModuleDef_HEAD_INIT, ++ "privileges", ++ pyprivileges__doc__, ++ -1, ++ module_methods, ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++ }; ++ ++ m = PyModule_Create(&moduledef); ++ if ( m == NULL ) ++ return m; ++ ++ PyObject* d = PyModule_GetDict(m); ++ if (d == NULL) ++ return m; ++ ++ PyDict_SetItemString(d, "PRIV_ON", PyLong_FromLong((long)PRIV_ON)); ++ PyDict_SetItemString(d, "PRIV_OFF", PyLong_FromLong((long)PRIV_OFF)); ++ PyDict_SetItemString(d, "PRIV_SET", PyLong_FromLong((long)PRIV_SET)); ++ ++ for (int i = 0; xlates[i].op != NULL; i++) { ++ PyDict_SetItemString(d, xlates[i].name, ++ PyLong_FromLong((long)xlates[i].cmd)); ++ } ++ ++ return m; ++} +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/pyrbac.c a/Modules/pyrbac.c +--- a~/Modules/pyrbac.c 1970-01-01 00:00:00 ++++ a/Modules/pyrbac.c 1970-01-01 00:00:00 +@@ -0,0 +1,81 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++ ++/* ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ */ ++ ++/* ++ * RBAC Bindings for Python ++ */ ++ ++#include ++#include "pyrbac.h" ++ ++static PyMethodDef module_methods[] = {NULL}; ++ ++PyDoc_STRVAR(pyrbac__doc__, "provides access to some objects \ ++for interaction with the Solaris Role-Based Access Control \ ++framework.\n\nDynamic objects:\n\ ++userattr -- for interacting with user_attr(5)\n\ ++authattr -- for interacting with auth_attr(5)\n\ ++execattr -- for interacting with exec_attr(5)\n"); ++ ++#ifndef PyMODINIT_FUNC /* declarations for DLL import/export */ ++#define PyMODINIT_FUNC void ++#endif ++PyMODINIT_FUNC ++PyInit_rbac (void) { ++ PyObject* m; ++ ++ if (PyType_Ready(&AuthattrType) < 0 || ++ PyType_Ready(&ExecattrType) < 0 || ++ PyType_Ready(&UserattrType) < 0 ) ++ return NULL; ++ ++ static struct PyModuleDef moduledef = { ++ PyModuleDef_HEAD_INIT, ++ "rbac", ++ pyrbac__doc__, ++ -1, ++ module_methods, ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++ }; ++ ++ m = PyModule_Create(&moduledef); ++ if ( m == NULL ) ++ return NULL; ++ ++ Py_INCREF(&AuthattrType); ++ PyModule_AddObject(m, "authattr", (PyObject*)&AuthattrType); ++ ++ Py_INCREF(&ExecattrType); ++ PyModule_AddObject(m, "execattr", (PyObject*)&ExecattrType); ++ ++ Py_INCREF(&UserattrType); ++ PyModule_AddObject(m, "userattr", (PyObject*)&UserattrType); ++ ++ return m; ++ ++} +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/pyrbac.h a/Modules/pyrbac.h +--- a~/Modules/pyrbac.h 1970-01-01 00:00:00 ++++ a/Modules/pyrbac.h 1970-01-01 00:00:00 +@@ -0,0 +1,45 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++ ++/* ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ */ ++ ++/* ++ * RBAC bindings for python ++ */ ++#ifndef PYRBAC_H ++#define PYRBAC_H ++ ++#include ++ ++ ++#define PYRBAC_USER_MODE 1 ++#define PYRBAC_PROF_MODE 2 ++#define PYRBAC_ATTR_MODE 3 ++#define PYRBAC_NAM_MODE 4 ++#define PYRBAC_UID_MODE 5 ++ ++extern PyTypeObject AuthattrType; ++extern PyTypeObject ExecattrType; ++extern PyTypeObject UserattrType; ++ ++#endif +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/userattr.c a/Modules/userattr.c +--- a~/Modules/userattr.c 1970-01-01 00:00:00 ++++ a/Modules/userattr.c 1970-01-01 00:00:00 +@@ -0,0 +1,301 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++ ++/* ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ */ ++ ++/* ++ * RBAC Bindings for Python - user_attr functions ++ */ ++ ++#include ++#include ++#include "Python.h" ++#include "pyrbac.h" ++ ++static PyObject* ++pyrbac_setuserattr(PyObject* self, PyObject* args) { ++ setuserattr(); ++ return Py_None; ++} ++ ++static PyObject* ++pyrbac_enduserattr(PyObject* self, PyObject* args) { ++ enduserattr(); ++ return Py_None; ++} ++ ++PyObject* ++pyrbac_getuseruidnamattr(PyObject* self, void* arg, int mode, char* filename) { ++ ++ userattr_t *ret_userattr = NULL; ++ ++ if (mode == PYRBAC_ATTR_MODE) { ++ if (filename != NULL) { ++ FILE* file = fopen(filename, "r"); ++ if (file == NULL) ++ return NULL; ++ ret_userattr = fgetuserattr(file); ++ if (fclose(file)) ++ return NULL; ++ } ++ else ++ ret_userattr = getuserattr(); ++ } ++ else if (mode == PYRBAC_NAM_MODE) ++ ret_userattr = getusernam((char*) arg); ++ else if (mode == PYRBAC_UID_MODE) ++ ret_userattr = getuseruid(*((uid_t*) arg)); ++ ++ if (ret_userattr == NULL) ++ return Py_None; ++ ++ PyObject* entry = PyTuple_New(5); ++ if (entry == NULL) { ++ free_userattr(ret_userattr); ++ return NULL; ++ } ++ ++ PyObject* kv_data = PyDict_New(); ++ ++ if(ret_userattr->attr != NULL) { ++ int len; ++ for(len = 0; len < ret_userattr->attr->length; len++) { ++ kv_t current = ret_userattr->attr->data[len]; ++ ++ PyObject* set = PyList_New(0); ++ char* saveptr; ++ char* item = strtok_r(current.value, ",", &saveptr); ++ PyList_Append(set, PyBytes_FromString(item)); ++ ++ while((item = strtok_r(NULL, ",", &saveptr)) != NULL) { ++ if(PyList_Append(set, PyBytes_FromString(item)) != 0) { ++ Py_XDECREF(set); ++ Py_XDECREF(kv_data); ++ free_userattr(ret_userattr); ++ return NULL; ++ } ++ } ++ if(PyDict_SetItemString(kv_data, current.key, set)) { ++ free_userattr(ret_userattr); ++ return NULL; ++ } ++ } ++ } ++ entry = Py_BuildValue("{s:s,s:s,s:s,s:s,s:O}", ++ "name", ret_userattr->name, ++ "qualifier", ret_userattr->qualifier, ++ "res1", ret_userattr->res1, ++ "res2", ret_userattr->res2, ++ "attributes", kv_data); ++ ++ free_userattr(ret_userattr); ++ ++ return entry; ++} ++ ++ ++static PyObject* ++pyrbac_getuserattr(PyObject* self, PyObject* args) { ++ return(pyrbac_getuseruidnamattr(self, (void*) NULL, PYRBAC_ATTR_MODE, NULL)); ++} ++ ++static PyObject* ++pyrbac_fgetuserattr(PyObject* self, PyObject* args) { ++ char* filename = NULL; ++ if(!PyArg_ParseTuple(args, "s:fgetuserattr", &filename)) ++ return NULL; ++ return(pyrbac_getuseruidnamattr(self, NULL, PYRBAC_ATTR_MODE, filename)); ++} ++ ++static PyObject* ++pyrbac_getusernam(PyObject* self, PyObject* args) { ++ char* name = NULL; ++ if(!PyArg_ParseTuple(args, "s:getusernam", &name)) ++ return NULL; ++ return(pyrbac_getuseruidnamattr(self, (void*) name, PYRBAC_NAM_MODE, NULL)); ++} ++ ++static PyObject* ++pyrbac_getuseruid(PyObject* self, PyObject* args) { ++ uid_t uid; ++ if(!PyArg_ParseTuple(args, "i:getuseruid", &uid)) ++ return NULL; ++ return(pyrbac_getuseruidnamattr(self, (void*) &uid, PYRBAC_UID_MODE, NULL)); ++} ++ ++static PyObject* ++pyrbac_userattr_next(PyObject* self) { ++ PyObject* retval = pyrbac_getuserattr(self, NULL); ++ if( retval == Py_None ) { ++ setuserattr(); ++ return NULL; ++ } ++ return retval; ++} ++static PyObject* ++pyrbac_userattr__iter__(PyObject* self) { ++ return self; ++} ++ ++typedef struct { ++ PyObject_HEAD ++} Userattr; ++ ++static void ++Userattr_dealloc(Userattr* self) { ++ enduserattr(); ++ Py_TYPE(self)->tp_free((PyObject*) self); ++} ++ ++static PyObject* ++Userattr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { ++ Userattr *self; ++ self = (Userattr*)type->tp_alloc(type, 0); ++ ++ return ((PyObject *) self); ++} ++ ++static int ++Userattr_init(Userattr* self, PyObject *args, PyObject *kwargs) { ++ setuserattr(); ++ return 0; ++} ++ ++PyDoc_STRVAR(pyrbac_userattr__doc__, "provides functions for \ ++interacting with the extended user attributes database. May be iterated over \ ++to enumerate user_attr(5) entries\n\n\ ++Methods provided:\n\ ++setuserattr\n\ ++enduserattr\n\ ++getuserattr\n\ ++fgetuserattr\n\ ++getusernam\n\ ++getuseruid"); ++ ++PyDoc_STRVAR(pyrbac_setuserattr__doc__, "\"rewinds\" the user_attr functions \ ++to the first entry in the db. Called automatically by the constructor.\n\ ++\tArguments: None\n\ ++\tReturns: None"); ++ ++PyDoc_STRVAR(pyrbac_enduserattr__doc__, "closes the user_attr database, \ ++cleans up storage. called automatically by the destructor\n\ ++\tArguments: None\n\ ++\tReturns: None"); ++ ++PyDoc_STRVAR(pyrbac_getuserattr__doc__, "Return a single user_attr entry\n \ ++\tArguments: None\n\ ++\tReturns: a dict representation of a userattr_t struct:\n\ ++\t\t\"name\": username\n\ ++\t\t\"qualifier\": reserved\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as either a list \ ++or a string depending on value" ++); ++ ++PyDoc_STRVAR(pyrbac_fgetuserattr__doc__, "Return a single user_attr entry \ ++from a file, bypassing nsswitch.conf\n\ ++\tArguments: \'filename\'\n\ ++\tReturns: a dict representation of a userattr_t struct:\n\ ++\t\t\"name\": username\n\ ++\t\t\"qualifier\": reserved\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as either a list \ ++or a string depending on value"); ++ ++PyDoc_STRVAR(pyrbac_getusernam__doc__, "Searches for a user_attr entry with a \ ++given user name\n\ ++\tArgument: \'username\'\n\ ++\tReturns: a dict representation of a userattr_t struct:\n\ ++\t\t\"name\": username\n\ ++\t\t\"qualifier\": reserved\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as either a list \ ++or a string depending on value"); ++ ++PyDoc_STRVAR(pyrbac_getuseruid__doc__, "Searches for a user_attr entry with a \ ++given uid\n\ ++\tArgument: uid\n\ ++\tReturns: a dict representation of a userattr_t struct:\n\ ++\t\t\"name\": username\n\ ++\t\t\"qualifier\": reserved\n\ ++\t\t\"res1\": reserved\n\ ++\t\t\"res2\": reserved\n\ ++\t\t\"attributes\": A Python dict keyed by attribute & valued as either a list \ ++or a string depending on value"); ++ ++static PyMethodDef Userattr_methods[] = { ++ {"setuserattr", pyrbac_setuserattr, METH_NOARGS, pyrbac_setuserattr__doc__}, ++ {"enduserattr", pyrbac_enduserattr, METH_NOARGS, pyrbac_enduserattr__doc__}, ++ {"getuserattr", pyrbac_getuserattr, METH_NOARGS, pyrbac_getuserattr__doc__}, ++ {"fgetuserattr", pyrbac_fgetuserattr, METH_VARARGS, pyrbac_fgetuserattr__doc__}, ++ {"getusernam", pyrbac_getusernam, METH_VARARGS, pyrbac_getusernam__doc__}, ++ {"getuseruid", pyrbac_getuseruid, METH_VARARGS, pyrbac_getuseruid__doc__}, ++ {NULL, NULL} ++}; ++ ++PyTypeObject UserattrType = { ++ PyVarObject_HEAD_INIT(NULL, 0) ++ "rbac.userattr", /*tp_name*/ ++ sizeof(Userattr), /*tp_basicsize*/ ++ 0, /*tp_itemsize*/ ++ (destructor)Userattr_dealloc, /*tp_dealloc*/ ++ 0, /*tp_print*/ ++ 0, /*tp_getattr*/ ++ 0, /*tp_setattr*/ ++ 0, /*tp_reserved*/ ++ 0, /*tp_repr*/ ++ 0, /*tp_as_number*/ ++ 0, /*tp_as_sequence*/ ++ 0, /*tp_as_mapping*/ ++ 0, /*tp_hash */ ++ 0, /*tp_call*/ ++ 0, /*tp_str*/ ++ 0, /*tp_getattro*/ ++ 0, /*tp_setattro*/ ++ 0, /*tp_as_buffer*/ ++ Py_TPFLAGS_DEFAULT | ++ Py_TPFLAGS_BASETYPE, /*tp_flags*/ ++ pyrbac_userattr__doc__, /* tp_doc */ ++ 0, /* tp_traverse */ ++ 0, /* tp_clear */ ++ 0, /* tp_richcompare */ ++ 0, /* tp_weaklistoffset */ ++ pyrbac_userattr__iter__, /* tp_iter */ ++ pyrbac_userattr_next, /* tp_iternext */ ++ Userattr_methods, /* tp_methods */ ++ 0, /* tp_members */ ++ 0, /* tp_getset */ ++ 0, /* tp_base */ ++ 0, /* tp_dict */ ++ 0, /* tp_descr_get */ ++ 0, /* tp_descr_set */ ++ 0, /* tp_dictoffset */ ++ (initproc)Userattr_init, /* tp_init */ ++ 0, /* tp_alloc */ ++ Userattr_new, /* tp_new */ ++ 0, /* tp_free */ ++ 0, /* tp_is_gc */ ++}; diff --git a/build/python313/patches/module-ucred.patch b/build/python313/patches/module-ucred.patch new file mode 100644 index 0000000000..688fe10bf7 --- /dev/null +++ b/build/python313/patches/module-ucred.patch @@ -0,0 +1,468 @@ +This patch provides Python ucred support. It may be contributed upstream at +some point, but the suitability (or lack thereof) has not yet been determined. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/ucredtest.py a/Lib/test/ucredtest.py +--- a~/Lib/test/ucredtest.py 1970-01-01 00:00:00 ++++ a/Lib/test/ucredtest.py 1970-01-01 00:00:00 +@@ -0,0 +1,45 @@ ++#!/usr/bin/python3 ++ ++import ucred ++import os ++ ++uc = ucred.get(os.getpid()) ++ ++print("pid = %d" % uc.getpid()) ++print("euid = %d" % uc.geteuid()) ++print("ruid = %d" % uc.getruid()) ++print("suid = %d" % uc.getsuid()) ++print("egid = %d" % uc.getegid()) ++print("rgid = %d" % uc.getrgid()) ++print("sgid = %d" % uc.getsgid()) ++print("zoneid = %d" % uc.getzoneid()) ++print("projid = %d" % uc.getprojid()) ++print("groups = %s" % uc.getgroups()) ++print("label = %s" % uc.getlabel()) ++ ++print("getpflags(0x1) = %d" % uc.getpflags(0x1)) ++print("getpflags(0x2) = %d" % uc.getpflags(0x2)) ++print("has_priv(Effective, proc_fork) = %d" % uc.has_priv("Effective", "proc_fork")) ++print("has_priv(Permitted, proc_fork) = %d" % uc.has_priv("Permitted", "proc_fork")) ++print("has_priv(Inheritable, proc_fork) = %d" % uc.has_priv("Inheritable", "proc_fork")) ++print("has_priv(Limit, file_setid) = %d" % uc.has_priv("Limit", "file_setid")) ++print("has_priv(Effective, file_setid) = %d" % uc.has_priv("Effective", "file_setid")) ++try: ++ uc.has_priv("Effective", "proc_bork") ++except OSError as e: ++ print(e) ++try: ++ uc.has_priv("Defective", "proc_fork") ++except OSError as e: ++ print(e) ++try: ++ uc.has_priv("Defective", "proc_bork") ++except OSError as e: ++ print(e) ++ ++del uc ++uc = ucred.ucred() ++try: ++ uc.getpid() ++except OSError as e: ++ print(e) +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/Setup.local a/Modules/Setup.local +--- a~/Modules/Setup.local 1970-01-01 00:00:00 ++++ a/Modules/Setup.local 1970-01-01 00:00:00 +@@ -0,0 +1,3 @@ ++# Edit this file for local setup changes ++ ++ucred ucred.c -ltsol +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/ucred.c a/Modules/ucred.c +--- a~/Modules/ucred.c 1970-01-01 00:00:00 ++++ a/Modules/ucred.c 1970-01-01 00:00:00 +@@ -0,0 +1,405 @@ ++/* ++ * Permission is hereby granted, free of charge, to any person obtaining a copy ++ * of this software and associated documentation files (the "Software"), to ++ * deal in the Software without restriction, including without limitation the ++ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ++ * sell copies of the Software, and to permit persons to whom the Software is ++ * furnished to do so, subject to the following conditions: ++ * ++ * The above copyright notice and this permission notice shall be included in ++ * all copies or substantial portions of the Software. ++ * ++ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ++ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ++ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ++ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ++ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ++ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ++ * DEALINGS IN THE SOFTWARE. ++ * ++ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. ++ * Copyright 2020 OmniOS Community Edition (OmniOSce) Association. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++typedef struct { ++ PyObject_HEAD ++ ucred_t *ucred; ++} pyucred_t; ++ ++#define pyucred_getlongid(name, type) \ ++ static PyObject * \ ++ pyucred_get##name(pyucred_t *uc) \ ++ { \ ++ long val; \ ++ \ ++ if (uc->ucred == NULL) { \ ++ errno = EINVAL; \ ++ PyErr_SetFromErrno(PyExc_OSError); \ ++ return (NULL); \ ++ } \ ++ \ ++ if ((val = ucred_get##name(uc->ucred)) == -1) { \ ++ PyErr_SetFromErrno(PyExc_OSError); \ ++ return (NULL); \ ++ } \ ++ \ ++ return (Py_BuildValue("l", (long)val)); \ ++ } ++ ++pyucred_getlongid(euid, uid_t) ++pyucred_getlongid(ruid, uid_t) ++pyucred_getlongid(suid, uid_t) ++pyucred_getlongid(egid, gid_t) ++pyucred_getlongid(rgid, gid_t) ++pyucred_getlongid(sgid, gid_t) ++pyucred_getlongid(pid, pid_t) ++pyucred_getlongid(projid, projid_t) ++pyucred_getlongid(zoneid, zoneid_t) ++ ++static PyObject * ++pyucred_getgroups(pyucred_t *uc) ++{ ++ const gid_t *groups; ++ PyObject *list; ++ int len; ++ int i; ++ ++ if (uc->ucred == NULL) { ++ errno = EINVAL; ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ if ((len = ucred_getgroups(uc->ucred, &groups)) == -1) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ if ((list = PyList_New(len)) == NULL) ++ return (NULL); ++ ++ for (i = 0; i < len; i++) { ++ PyObject *gid = Py_BuildValue("l", (long)groups[i]); ++ if (PyList_SetItem(list, i, gid) == -1) ++ return (NULL); ++ } ++ ++ return (list); ++} ++ ++static PyObject * ++pyucred_getlabel(pyucred_t *uc) ++{ ++ m_label_t *label; ++ PyObject *ret; ++ char *str; ++ ++ if (uc->ucred == NULL) { ++ errno = EINVAL; ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ label = ucred_getlabel(uc->ucred); ++ if (label == NULL) ++ return (Py_BuildValue("s", "")); ++ ++ if (label_to_str(label, &str, M_LABEL, DEF_NAMES) == -1) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ ret = Py_BuildValue("s", str); ++ free(str); ++ return (ret); ++} ++ ++static PyObject * ++pyucred_getpflags(pyucred_t *uc, PyObject *args, PyObject *kwargs) ++{ ++ static char *kwlist[] = { "flags", NULL }; ++ uint_t flags; ++ ++ if (uc->ucred == NULL) { ++ errno = EINVAL; ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", kwlist, ++ &flags)) ++ return (NULL); ++ ++ if ((flags = ucred_getpflags(uc->ucred, flags)) == (uint_t)-1) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ return (Py_BuildValue("i", flags)); ++} ++ ++static PyObject * ++pyucred_has_priv(pyucred_t *uc, PyObject *args, PyObject *kwargs) ++{ ++ static char *kwlist[] = { "set", "priv", NULL }; ++ const priv_set_t *privs; ++ const char *set; ++ const char *priv; ++ ++ if (uc->ucred == NULL) { ++ errno = EINVAL; ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss", kwlist, ++ &set, &priv)) ++ return (NULL); ++ ++ if ((privs = ucred_getprivset(uc->ucred, set)) == NULL) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ if (priv_ismember(privs, priv)) { ++ Py_INCREF(Py_True); ++ return Py_True; ++ } ++ ++ Py_INCREF(Py_False); ++ return Py_False; ++} ++ ++PyDoc_STRVAR(pyucred_getlabel_doc, ++ "getlabel() -> string\n" ++ "\n" ++ "Return the Trusted Extensions label string, or an " ++ "empty string if not available. The label string is " ++ "converted using the default name and M_LABEL (human-readable). " ++ "Raises OSError. See label_to_str(3TSOL)."); ++PyDoc_STRVAR(pyucred_getpflags_doc, ++ "getpflags(flags) -> int\n" ++ "\n" ++ "Return the values of the specified privilege flags."); ++PyDoc_STRVAR(pyucred_has_priv_doc, ++ "has_priv(set, priv) -> bool\n" ++ "\n" ++ "Return true if the given privilege is set in the " ++ "specified set. Raises OSError if the set or privilege is " ++ "invalid, or a problem occurs.\n" ++ "\n" ++ "Currently, the following privilege sets are defined, as " ++ "described in privileges(7):\n" ++ "\n" ++ "Effective\n" ++ "Permitted\n" ++ "Inheritable\n" ++ "Limit\n"); ++ ++static PyMethodDef pyucred_methods[] = { ++ { "geteuid", (PyCFunction)(uintptr_t)pyucred_geteuid, METH_NOARGS, ++ "Return the effective user ID." }, ++ { "getruid", (PyCFunction)(uintptr_t)pyucred_getruid, METH_NOARGS, ++ "Return the real user ID." }, ++ { "getsuid", (PyCFunction)(uintptr_t)pyucred_getsuid, METH_NOARGS, ++ "Return the saved user ID." }, ++ { "getegid", (PyCFunction)(uintptr_t)pyucred_getegid, METH_NOARGS, ++ "Return the effective group ID." }, ++ { "getrgid", (PyCFunction)(uintptr_t)pyucred_getrgid, METH_NOARGS, ++ "Return the real group ID." }, ++ { "getsgid", (PyCFunction)(uintptr_t)pyucred_getsgid, METH_NOARGS, ++ "Return the saved group ID." }, ++ { "getpid", (PyCFunction)(uintptr_t)pyucred_getpid, METH_NOARGS, ++ "Return the effective user ID." }, ++ { "getprojid", (PyCFunction)(uintptr_t)pyucred_getprojid, METH_NOARGS, ++ "Return the project ID." }, ++ { "getzoneid", (PyCFunction)(uintptr_t)pyucred_getzoneid, METH_NOARGS, ++ "Return the zone ID." }, ++ { "getgroups", (PyCFunction)(uintptr_t)pyucred_getgroups, METH_NOARGS, ++ "Return a list of group IDs." }, ++ { "getlabel", (PyCFunction)(uintptr_t)pyucred_getlabel, METH_NOARGS, ++ pyucred_getlabel_doc }, ++ { "getpflags", (PyCFunction)(uintptr_t)pyucred_getpflags, ++ METH_VARARGS|METH_KEYWORDS, pyucred_getpflags_doc }, ++ { "has_priv", (PyCFunction)(uintptr_t)pyucred_has_priv, ++ METH_VARARGS|METH_KEYWORDS, pyucred_has_priv_doc }, ++ { NULL, NULL } ++}; ++ ++static int ++pyucred_init(PyObject *self, PyObject *args, PyObject *kwargs) ++{ ++ pyucred_t *uc = (pyucred_t *)self; ++ uc->ucred = NULL; ++ return (0); ++} ++ ++static void ++pyucred_dealloc(PyObject *self) ++{ ++ pyucred_t *uc = (pyucred_t *)self; ++ if (uc->ucred != NULL) ++ ucred_free(uc->ucred); ++ Py_TYPE(self)->tp_free(self); ++} ++ ++static PyTypeObject pyucred_type = { ++ PyVarObject_HEAD_INIT(NULL, 0) ++ "ucred.ucred", /*tp_name*/ ++ sizeof (pyucred_t), /*tp_basicsize*/ ++ 0, /*tp_itemsize*/ ++ pyucred_dealloc, /*tp_dealloc*/ ++ 0, /*tp_print*/ ++ 0, /*tp_getattr*/ ++ 0, /*tp_setattr*/ ++ 0, /*tp_reserved*/ ++ 0, /*tp_repr*/ ++ 0, /*tp_as_number*/ ++ 0, /*tp_as_sequence*/ ++ 0, /*tp_as_mapping*/ ++ 0, /*tp_hash */ ++ 0, /*tp_call*/ ++ 0, /*tp_str*/ ++ 0, /*tp_getattro*/ ++ 0, /*tp_setattro*/ ++ 0, /*tp_as_buffer*/ ++ Py_TPFLAGS_DEFAULT, /*tp_flags*/ ++ "user credentials", /*tp_doc */ ++ 0, /* tp_traverse */ ++ 0, /* tp_clear */ ++ 0, /* tp_richcompare */ ++ 0, /* tp_weaklistoffset */ ++ 0, /* tp_iter */ ++ 0, /* tp_iternext */ ++ pyucred_methods, /* tp_methods */ ++ 0, /* tp_members */ ++ 0, /* tp_getset */ ++ 0, /* tp_base */ ++ 0, /* tp_dict */ ++ 0, /* tp_descr_get */ ++ 0, /* tp_descr_set */ ++ 0, /* tp_dictoffset */ ++ (initproc)pyucred_init, /* tp_init */ ++ 0, /* tp_alloc */ ++ 0, /* tp_new */ ++ 0, /* tp_free */ ++ 0, /* tp_is_gc */ ++}; ++ ++static PyObject * ++pyucred_new(const ucred_t *uc) ++{ ++ pyucred_t *self; ++ ++ self = (pyucred_t *)PyObject_CallObject((PyObject *)&pyucred_type, NULL); ++ ++ if (self == NULL) ++ return (NULL); ++ ++ self->ucred = (ucred_t *)uc; ++ ++ return ((PyObject *)self); ++} ++ ++static PyObject * ++pyucred_get(PyObject *o, PyObject *args, PyObject *kwargs) ++{ ++ static char *kwlist[] = { "pid", NULL }; ++ ucred_t *ucred = NULL; ++ int pid; ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", kwlist, ++ &pid)) ++ return (NULL); ++ ++ ucred = ucred_get(pid); ++ ++ if (ucred == NULL) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ return (pyucred_new(ucred)); ++} ++ ++static PyObject * ++pyucred_getpeer(PyObject *o, PyObject *args, PyObject *kwargs) ++{ ++ static char *kwlist[] = { "fd", NULL }; ++ ucred_t *ucred = NULL; ++ int fd; ++ ++ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i", kwlist, ++ &fd)) ++ return (NULL); ++ ++ if (getpeerucred(fd, &ucred) == -1) { ++ PyErr_SetFromErrno(PyExc_OSError); ++ return (NULL); ++ } ++ ++ return (pyucred_new(ucred)); ++} ++ ++PyDoc_STRVAR(pyucred_get_doc, ++ "get(pid) -> ucred\n" ++ "\n" ++ "Return the credentials of the specified process ID. " ++ "Raises OSError. See ucred_get(3C)."); ++PyDoc_STRVAR(pyucred_getpeer_doc, ++ "getpeer(fd) -> ucred\n" ++ "\n" ++ "Return the credentials of the peer endpoint of a " ++ "connection-oriented socket (SOCK_STREAM) or STREAM fd " ++ "at the time the endpoint was created or the connection " ++ "was established. Raises OSError. See getpeerucred(3C)."); ++ ++static struct PyMethodDef pyucred_module_methods[] = { ++ { "get", (PyCFunction)(uintptr_t)pyucred_get, ++ METH_VARARGS|METH_KEYWORDS, pyucred_get_doc }, ++ { "getpeer", (PyCFunction)(uintptr_t)pyucred_getpeer, ++ METH_VARARGS|METH_KEYWORDS, pyucred_getpeer_doc }, ++ { NULL, NULL, 0, NULL } ++}; ++ ++PyDoc_STRVAR(pyucred_module_doc, ++ "This module provides an interface to the user credential access " ++ "methods, obtainable either by process ID or file descriptor."); ++ ++PyMODINIT_FUNC ++PyInit_ucred (void) ++{ ++ PyObject *m; ++ ++ static struct PyModuleDef moduledef = { ++ PyModuleDef_HEAD_INIT, ++ "ucred", ++ pyucred_module_doc, ++ -1, ++ pyucred_module_methods, ++ NULL, ++ NULL, ++ NULL, ++ NULL, ++ }; ++ ++ m = PyModule_Create(&moduledef); ++ ++ pyucred_type.tp_new = PyType_GenericNew; ++ if (PyType_Ready(&pyucred_type) < 0) ++ return NULL; ++ ++ Py_INCREF(&pyucred_type); ++ ++ PyModule_AddObject(m, "ucred", (PyObject *)&pyucred_type); ++ ++ return m; ++} diff --git a/build/python313/patches/py_db.patch b/build/python313/patches/py_db.patch new file mode 100644 index 0000000000..da3953121a --- /dev/null +++ b/build/python313/patches/py_db.patch @@ -0,0 +1,775 @@ +This patch adds Python debugger support. It may be contributed upstream at +some point, but the suitability (or lack thereof) has not yet been determined. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Makefile.pre.in a/Makefile.pre.in +--- a~/Makefile.pre.in 1970-01-01 00:00:00 ++++ a/Makefile.pre.in 1970-01-01 00:00:00 +@@ -614,7 +614,7 @@ all: @DEF_MAKE_ALL_RULE@ + .PHONY: all + + .PHONY: build_all +-build_all: check-clean-src $(BUILDPYTHON) platform sharedmods \ ++build_all: check-clean-src $(BUILDPYTHON) platform sharedmods build-py_db \ + gdbhooks Programs/_testembed scripts checksharedmods rundsymutil + + .PHONY: build_wasm +@@ -828,6 +828,15 @@ libpython3.so: libpython$(LDVERSION).so + libpython$(LDVERSION).dylib: $(LIBRARY_OBJS) + $(CC) -dynamiclib -Wl,-single_module $(PY_CORE_LDFLAGS) -undefined dynamic_lookup -Wl,-install_name,$(prefix)/lib/libpython$(LDVERSION).dylib -Wl,-compatibility_version,$(VERSION) -Wl,-current_version,$(VERSION) -o $@ $(LIBRARY_OBJS) $(DTRACE_OBJS) $(SHLIBS) $(LIBC) $(LIBM); \ + ++SHLIB_FLAGS = -shared -fpic ++ ++libpython$(LDVERSION)_db.so.1.0: $(srcdir)/py_db/libpython312_db.c ++ $(CC) -o $@ $(CFLAGS) $(PY_CFLAGS) $(PY_CPPFLAGS) $(CPPFLAGS) $(SHLIB_FLAGS) $< ++ ++build-py_db: libpython$(LDVERSION)_db.so.1.0 ++ ++install-py_db: libpython$(LDVERSION)_db.so.1.0 ++ $(INSTALL_SHARED) libpython$(LDVERSION)_db.so.1.0 $(DESTDIR)$(LIBDIR) + + libpython$(VERSION).sl: $(LIBRARY_OBJS) + $(LDSHARED) -o $@ $(LIBRARY_OBJS) $(MODLIBS) $(SHLIBS) $(LIBC) $(LIBM) +@@ -1900,7 +1909,7 @@ multissltest: all + # which can lead to two parallel `./python setup.py build` processes that + # step on each others toes. + .PHONY: install +-install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall @FRAMEWORKINSTALLLAST@ ++install: @FRAMEWORKINSTALLFIRST@ commoninstall bininstall maninstall install-py_db @FRAMEWORKINSTALLLAST@ + if test "x$(ENSUREPIP)" != "xno" ; then \ + case $(ENSUREPIP) in \ + upgrade) ensurepip="--upgrade" ;; \ +diff -wpruN --no-dereference '--exclude=*.orig' a~/py_db/libpython312_db.c a/py_db/libpython312_db.c +--- a~/py_db/libpython312_db.c 1970-01-01 00:00:00 ++++ a/py_db/libpython312_db.c 1970-01-01 00:00:00 +@@ -0,0 +1,611 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++/* ++ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#define Py_BUILD_CORE ++ ++#include ++#include ++#include "internal/pycore_runtime.h" ++#include "internal/pycore_interp.h" ++ ++#include "libpython312_db.h" ++ ++struct pydb_agent { ++ struct ps_prochandle *pdb_ph; ++ int pdb_vers; ++ int pdb_is_64bit; ++ int pdb_datamodel; ++}; ++ ++typedef uintptr_t (*pdi_next_cb_t)(pydb_iter_t *); ++ ++struct pydb_iter { ++ struct ps_prochandle *pdi_ph; ++ uintptr_t pdi_current; ++ pdi_next_cb_t pdi_nextf; ++}; ++ ++#define LIBPYTHON "libpython3.12.so" ++ ++#define MIN(x, y) (((x) < (y)) ? (x) : (y)) ++ ++/* Generic interface to helper functions */ ++static ssize_t pydb_strobj_readdata(pydb_agent_t *py, uintptr_t addr, ++ unsigned char *buf, size_t buf_len); ++static int pydb_getlno(pydb_agent_t *py, uintptr_t lnotab_addr, int firstline, ++ int lastinst); ++static int pydb_frameinfo(pydb_agent_t *py, uintptr_t addr, char *funcnm, ++ size_t funcnm_sz, char *filenm, size_t filenm_sz, int *lineno); ++ ++/* datamodel specific implementation of helper functions */ ++static ssize_t pydb_strobj_readdata_native(pydb_agent_t *py, uintptr_t addr, ++ unsigned char *buf, size_t buf_len); ++static int pydb_frameinfo_native(pydb_agent_t *py, uintptr_t addr, char *funcnm, ++ size_t funcnm_sz, char *filenm, size_t filenm_sz, int *lineno); ++ ++static ssize_t pydb_strobj_readstr(pydb_agent_t *py, uintptr_t addr, char *buf, ++ size_t len); ++ ++/* Iterator function next routines. Plugable, configured by iterator init */ ++static uintptr_t pydb_frame_iter_next(pydb_iter_t *iter); ++static uintptr_t pydb_interp_iter_next(pydb_iter_t *iter); ++static uintptr_t pydb_thread_iter_next(pydb_iter_t *iter); ++ ++static const char *strbasename(const char *s); ++ ++static const char * ++strbasename(const char *s) ++{ ++ const char *p = strrchr(s, '/'); ++ ++ if (p == NULL) ++ return (s); ++ ++ return (++p); ++} ++ ++/* Agent creation / destruction routines */ ++ ++pydb_agent_t * ++pydb_agent_create(struct ps_prochandle *P, int vers) ++{ ++ pydb_agent_t *py; ++ int datamodel; ++ ++ if (vers != PYDB_VERSION) { ++ errno = ENOTSUP; ++ return (NULL); ++ } ++ ++ if (ps_pdmodel(P, &datamodel) != PS_OK) { ++ return (NULL); ++ } ++ ++ py = (pydb_agent_t *)malloc(sizeof (pydb_agent_t)); ++ if (py == NULL) { ++ return (NULL); ++ } ++ ++ py->pdb_ph = P; ++ py->pdb_vers = vers; ++ py->pdb_datamodel = datamodel; ++ py->pdb_is_64bit = 0; ++ ++ return (py); ++} ++ ++void ++pydb_agent_destroy(pydb_agent_t *py) ++{ ++ if (py == NULL) { ++ return; ++ } ++ ++ free(py); ++} ++ ++/* Helper functions */ ++static int ++pydb_getlno(pydb_agent_t *py, uintptr_t lnotab_addr, int firstline, ++ int lastinst) ++{ ++ unsigned char lnotab[4096]; ++ ssize_t lnotab_len; ++ int addr, line; ++ int i; ++ ++ lnotab_len = pydb_strobj_readdata(py, lnotab_addr, lnotab, ++ sizeof (lnotab)); ++ if (lnotab_len < 0) { ++ return (-1); ++ } ++ ++ /* ++ * Python's line number algorithm is arcane. See here for details: ++ * http://svn.python.org/projects/python/trunk/Objects/lnotab_notes.txt ++ */ ++ ++ line = firstline; ++ for (addr = i = 0; i < lnotab_len; i += 2) { ++ if (addr + lnotab[i] > lastinst) { ++ break; ++ } ++ addr += lnotab[i]; ++ line += lnotab[i + 1]; ++ } ++ ++ return (line); ++} ++ ++static ssize_t ++pydb_asciiobj_readdata(pydb_agent_t *py, uintptr_t addr, ++ unsigned char *buf, size_t buf_len) ++{ ++ PyASCIIObject sobj; ++ ssize_t obj_sz; ++ ssize_t read_sz; ++ psaddr_t asciiaddr; ++ ++ /* ++ * PyASCIIObjects are a type of Unicode string. They are identified ++ * as follows: ++ * - sobj.state.compact == 1 ++ * - sobj.state.ascii == 1 ++ * The length of the string is stored in sobj.length. The string ++ * itself follows the PyASCIIObject. ++ */ ++ ++ if (ps_pread(py->pdb_ph, addr, &sobj, sizeof (PyASCIIObject)) ++ != PS_OK) { ++ return (-1); ++ } ++ ++ if (!sobj.state.compact || !sobj.state.ascii) { ++ return (-1); ++ } ++ ++ obj_sz = (ssize_t)sobj.length; ++ ++ read_sz = MIN(obj_sz, (ssize_t)buf_len); ++ asciiaddr = (psaddr_t)(addr + sizeof (PyASCIIObject)); ++ ++ if (ps_pread(py->pdb_ph, asciiaddr, buf, (size_t)read_sz) != PS_OK) { ++ return (-1); ++ } ++ ++ return (read_sz); ++} ++ ++static ssize_t ++pydb_asciiobj_readstr(pydb_agent_t *py, uintptr_t addr, char *buf, ++ size_t buf_len) ++{ ++ ssize_t read_sz; ++ ++ read_sz = pydb_asciiobj_readdata(py, addr, (unsigned char *)buf, ++ buf_len); ++ ++ if (read_sz >= 0) { ++ if (read_sz >= buf_len) { ++ read_sz = buf_len - 1; ++ } ++ ++ buf[read_sz] = '\0'; ++ } ++ ++ return (read_sz); ++} ++ ++static ssize_t ++pydb_strobj_readdata(pydb_agent_t *py, uintptr_t addr, ++ unsigned char *buf, size_t buf_len) ++{ ++ PyBytesObject sobj; ++ ssize_t obj_sz; ++ ssize_t read_sz; ++ psaddr_t straddr; ++ ++ /* ++ * PyBytesObject are variable size. The size of the PyBytesObject ++ * struct is fixed, and known at compile time; however, the size of the ++ * associated buffer is variable. The char[1] element at the end of the ++ * structure contains the string, and the ob_size of the PyBytesObject ++ * indicates how much extra space was allocated to contain the string ++ * buffer at the object's tail. Read in the fixed size portion of the ++ * object first, and then read the contents of the data buffer into the ++ * buffer passed by the caller. ++ */ ++ ++ if (ps_pread(py->pdb_ph, addr, &sobj, sizeof (PyBytesObject)) ++ != PS_OK) { ++ return (-1); ++ } ++ ++ /* ++ * If we want to emulate PyBytes_GET_SIZE() instead of just calling ++ * Py_SIZE() directly, we need to do a ps_pread() of Py_TYPE(&sobj). ++ * PyBytes_Check() will try to access the type structure, but the ++ * address is not in the debugger's address space. ++ */ ++ obj_sz = (ssize_t)Py_SIZE(&sobj); ++ ++ read_sz = MIN(obj_sz, (ssize_t)buf_len); ++ straddr = (psaddr_t)(addr + offsetof(PyBytesObject, ob_sval)); ++ ++ if (ps_pread(py->pdb_ph, straddr, buf, (size_t)read_sz) != PS_OK) { ++ return (-1); ++ } ++ ++ return (read_sz); ++} ++ ++/* ++ * Most Python PyBytesObject contain strings, as one would expect. However, ++ * due to some sleazy hackery in parts of the Python code, some string objects ++ * are used as buffers for binary data. In the general case, ++ * pydb_strobj_readstr() should be used to read strings out of string objects. ++ * It wraps pydb_strobj_readdata(), which should be used by callers only when ++ * trying to retrieve binary data. (This routine does some string cleanup). ++ */ ++static ssize_t ++pydb_strobj_readstr(pydb_agent_t *py, uintptr_t addr, char *buf, ++ size_t buf_len) ++{ ++ ssize_t read_sz; ++ ++ read_sz = pydb_strobj_readdata(py, addr, (unsigned char *)buf, buf_len); ++ ++ if (read_sz >= 0) { ++ if (read_sz >= buf_len) { ++ read_sz = buf_len - 1; ++ } ++ ++ buf[read_sz] = '\0'; ++ } ++ ++ return (read_sz); ++} ++ ++ ++static int ++pydb_frameinfo(pydb_agent_t *py, uintptr_t addr, char *funcnm, ++ size_t funcnm_sz, char *filenm, size_t filenm_sz, int *lineno) ++{ ++ PyFrameObject fo; ++ _PyInterpreterFrame ifo; ++ PyCodeObject co; ++ ssize_t rc; ++ int lasti; ++ ++ if (ps_pread(py->pdb_ph, addr, &fo, sizeof (PyFrameObject)) ++ != PS_OK) { ++ return (-1); ++ } ++ ++ if (ps_pread(py->pdb_ph, (uintptr_t)fo.f_frame, &ifo, sizeof (_PyInterpreterFrame)) ++ != PS_OK) { ++ return (-1); ++ } ++ ++ if (ps_pread(py->pdb_ph, (uintptr_t)ifo.f_code, &co, ++ sizeof (PyCodeObject)) != PS_OK) { ++ return (-1); ++ } ++ ++ rc = pydb_asciiobj_readstr(py, (uintptr_t)co.co_name, funcnm, ++ funcnm_sz); ++ if (rc < 0) { ++ return (-1); ++ } ++ ++ rc = pydb_asciiobj_readstr(py, (uintptr_t)co.co_filename, filenm, ++ filenm_sz); ++ if (rc < 0) { ++ return (-1); ++ } ++ ++ lasti = (int)(ifo.prev_instr - (_Py_CODEUNIT *)co.co_code_adaptive); ++ ++ *lineno = pydb_getlno(py, (uintptr_t)co.co_linetable, co.co_firstlineno, ++ lasti); ++ if (*lineno < 0) { ++ return (-1); ++ } ++ ++ return (0); ++} ++ ++/* Functions that are part of the library's interface */ ++ ++/* ++ * Given the address of a PyFrameObject, and a buffer of a known size, ++ * fill the buffer with a description of the frame. ++ */ ++int ++pydb_get_frameinfo(pydb_agent_t *py, uintptr_t frame_addr, char *fbuf, ++ size_t bufsz, int verbose) ++{ ++ char funcname[1024]; ++ char filename[1024]; ++ char *fn; ++ int lineno; ++ int length = (py->pdb_is_64bit ? 16 : 8); ++ int rc; ++ ++ rc = pydb_frameinfo(py, frame_addr, funcname, sizeof (funcname), ++ filename, sizeof (filename), &lineno); ++ if (rc < 0) { ++ return (-1); ++ } ++ ++ if (!verbose) { ++ fn = (char *)strbasename(filename); ++ } else { ++ fn = filename; ++ } ++ ++ (void) snprintf(fbuf, bufsz, "%0.*lx %s:%d %s()\n", length, ++ frame_addr, fn, lineno, funcname); ++ ++ return (0); ++} ++ ++/* ++ * Return a description about a PyFrameObject, if the object is ++ * actually a PyFrameObject. In this case, the pc argument is checked ++ * to make sure that it came from a function that takes a PyFrameObject ++ * as its first (argv[0]) argument. ++ */ ++int ++pydb_pc_frameinfo(pydb_agent_t *py, uintptr_t pc, uintptr_t frame_addr, ++ char *fbuf, size_t bufsz) ++{ ++ char funcname[1024]; ++ char filename[1024]; ++ int lineno; ++ int rc; ++ ps_sym_t psym; ++ ++ /* ++ * If PC doesn't match PyEval_EvalFrameEx in either libpython ++ * or the executable, don't decode it. ++ */ ++ if (ps_pglobal_sym(py->pdb_ph, LIBPYTHON, "PyEval_EvalFrameEx", &psym) ++ != PS_OK) { ++ return (-1); ++ } ++ ++ /* If symbol found, ensure that PC falls within PyEval_EvalFrameEx. */ ++ if (pc < psym.st_value || pc > psym.st_value + psym.st_size) { ++ return (-1); ++ } ++ ++ rc = pydb_frameinfo(py, frame_addr, funcname, sizeof (funcname), ++ filename, sizeof (filename), &lineno); ++ if (rc < 0) { ++ return (-1); ++ } ++ ++ (void) snprintf(fbuf, bufsz, "[ %s:%d (%s) ]\n", filename, lineno, ++ funcname); ++ ++ return (0); ++} ++ ++/* ++ * Walks the list of PyInterpreterState objects. If caller doesn't ++ * supply address of list, this method will look it up. ++ */ ++pydb_iter_t * ++pydb_interp_iter_init(pydb_agent_t *py, uintptr_t addr) ++{ ++ pydb_iter_t *itr; ++ _PyRuntimeState st; ++ uintptr_t i_addr; ++ int rc; ++ ++ if (addr == 0) { ++ rc = ps_pglobal_lookup(py->pdb_ph, LIBPYTHON, "_PyRuntime", ++ (psaddr_t *)&addr); ++ if (rc != PS_OK) { ++ return (NULL); ++ } ++ ++ if (ps_pread(py->pdb_ph, (uintptr_t)addr, &st, sizeof (_PyRuntimeState)) ++ != PS_OK) { ++ return (NULL); ++ } ++ ++ i_addr = (uintptr_t)st.interpreters.head; ++ ++ } else { ++ if (ps_pread(py->pdb_ph, (uintptr_t)addr, &i_addr, sizeof (uintptr_t)) ++ != PS_OK) { ++ return (NULL); ++ } ++ } ++ ++ itr = malloc(sizeof (pydb_iter_t)); ++ if (itr == NULL) { ++ return (NULL); ++ } ++ ++ itr->pdi_ph = py->pdb_ph; ++ itr->pdi_current = i_addr; ++ itr->pdi_nextf = pydb_interp_iter_next; ++ ++ return (itr); ++} ++ ++static uintptr_t ++pydb_interp_iter_next(pydb_iter_t *iter) ++{ ++ PyInterpreterState st; ++ uintptr_t cur; ++ ++ cur = iter->pdi_current; ++ ++ if (cur == 0) { ++ return (cur); ++ } ++ ++ if (ps_pread(iter->pdi_ph, cur, &st, sizeof (PyInterpreterState)) ++ != PS_OK) { ++ iter->pdi_current = 0; ++ return (0); ++ } ++ ++ iter->pdi_current = (uintptr_t)st.next; ++ ++ return (cur); ++} ++ ++/* ++ * Walk a list of Python PyFrameObjects. The addr argument must be ++ * the address of a valid PyThreadState object. ++ */ ++pydb_iter_t * ++pydb_frame_iter_init(pydb_agent_t *py, uintptr_t addr) ++{ ++ pydb_iter_t *itr; ++ PyThreadState ts; ++ _PyCFrame cf; ++ ++ if (ps_pread(py->pdb_ph, (uintptr_t)addr, &ts, sizeof (PyThreadState)) ++ != PS_OK) { ++ return (NULL); ++ } ++ ++ if (ps_pread(py->pdb_ph, (uintptr_t)ts.cframe, &cf, sizeof (_PyCFrame)) ++ != PS_OK) { ++ return (NULL); ++ } ++ ++ ++ itr = malloc(sizeof (pydb_iter_t)); ++ if (itr == NULL) { ++ return (NULL); ++ } ++ ++ itr->pdi_ph = py->pdb_ph; ++ itr->pdi_current = (uintptr_t)cf.current_frame; ++ itr->pdi_nextf = pydb_frame_iter_next; ++ ++ return (itr); ++} ++ ++static uintptr_t ++pydb_frame_iter_next(pydb_iter_t *iter) ++{ ++ PyFrameObject fo; ++ uintptr_t cur; ++ ++ cur = iter->pdi_current; ++ ++ if (cur == 0) { ++ return (cur); ++ } ++ ++ if (ps_pread(iter->pdi_ph, cur, &fo, sizeof (PyFrameObject)) ++ != PS_OK) { ++ iter->pdi_current = 0; ++ return (0); ++ } ++ ++ iter->pdi_current = (uintptr_t)fo.f_back; ++ ++ return (cur); ++} ++ ++/* ++ * Walk a list of Python PyThreadState objects. The addr argument must be ++ * the address of a valid PyInterpreterState object. ++ */ ++pydb_iter_t * ++pydb_thread_iter_init(pydb_agent_t *py, uintptr_t addr) ++{ ++ pydb_iter_t *itr; ++ PyInterpreterState is; ++ ++ if (ps_pread(py->pdb_ph, (uintptr_t)addr, &is, ++ sizeof (PyInterpreterState)) != PS_OK) { ++ return (NULL); ++ } ++ ++ itr = malloc(sizeof (pydb_iter_t)); ++ if (itr == NULL) { ++ return (NULL); ++ } ++ ++ itr->pdi_ph = py->pdb_ph; ++ itr->pdi_current = (uintptr_t)is.threads.head; ++ itr->pdi_nextf = pydb_thread_iter_next; ++ ++ return (itr); ++} ++ ++static uintptr_t ++pydb_thread_iter_next(pydb_iter_t *iter) ++{ ++ PyThreadState ts; ++ uintptr_t cur; ++ ++ cur = iter->pdi_current; ++ ++ if (cur == 0) { ++ return (cur); ++ } ++ ++ if (ps_pread(iter->pdi_ph, cur, &ts, sizeof (PyThreadState)) != PS_OK) { ++ iter->pdi_current = 0; ++ return (0); ++ } ++ ++ iter->pdi_current = (uintptr_t)ts.next; ++ ++ return (cur); ++} ++ ++ ++uintptr_t ++pydb_iter_next(pydb_iter_t *iter) ++{ ++ return (iter->pdi_nextf(iter)); ++} ++ ++void ++pydb_iter_fini(pydb_iter_t *iter) ++{ ++ if (iter == NULL) { ++ return; ++ } ++ ++ free(iter); ++} +diff -wpruN --no-dereference '--exclude=*.orig' a~/py_db/libpython312_db.h a/py_db/libpython312_db.h +--- a~/py_db/libpython312_db.h 1970-01-01 00:00:00 ++++ a/py_db/libpython312_db.h 1970-01-01 00:00:00 +@@ -0,0 +1,73 @@ ++/* ++ * CDDL HEADER START ++ * ++ * The contents of this file are subject to the terms of the ++ * Common Development and Distribution License (the "License"). ++ * You may not use this file except in compliance with the License. ++ * ++ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++ * or http://www.opensolaris.org/os/licensing. ++ * See the License for the specific language governing permissions ++ * and limitations under the License. ++ * ++ * When distributing Covered Code, include this CDDL HEADER in each ++ * file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++ * If applicable, add the following below this CDDL HEADER, with the ++ * fields enclosed by brackets "[]" replaced with your own identifying ++ * information: Portions Copyright [yyyy] [name of copyright owner] ++ * ++ * CDDL HEADER END ++ */ ++/* ++ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. ++ */ ++ ++#ifndef _LIBPYTHON312_DB_H ++#define _LIBPYTHON312_DB_H ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++/* Agent is opaque to library's consumers. */ ++typedef struct pydb_agent pydb_agent_t; ++ ++/* ++ * Library's debug version is 1. Changes to interface should increase this ++ * number. ++ */ ++#define PYDB_VERSION 1 ++ ++/* Agent creation/destruction routines */ ++extern pydb_agent_t *pydb_agent_create(struct ps_prochandle *P, int vers); ++extern void pydb_agent_destroy(pydb_agent_t *py); ++ ++/* Used by callers that know they are looking at a PyFrameObject */ ++extern int pydb_get_frameinfo(pydb_agent_t *py, uintptr_t frame_addr, ++ char *fbuf, size_t bufsz, int verbose); ++ ++/* ++ * Used by callers that don't know if they're looking at PyFrameObject. ++ * Checks PC for traceable functions. ++ */ ++extern int pydb_pc_frameinfo(pydb_agent_t *py, uintptr_t pc, ++ uintptr_t frame_addr, char *fbuf, size_t bufsz); ++ ++/* Iterator functions */ ++typedef struct pydb_iter pydb_iter_t; ++ ++extern pydb_iter_t *pydb_frame_iter_init(pydb_agent_t *py, uintptr_t addr); ++extern pydb_iter_t *pydb_interp_iter_init(pydb_agent_t *py, ++ uintptr_t addr); ++extern pydb_iter_t *pydb_thread_iter_init(pydb_agent_t *py, ++ uintptr_t addr); ++extern void pydb_iter_fini(pydb_iter_t *iter); ++extern uintptr_t pydb_iter_next(pydb_iter_t *iter); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* _LIBPYTHON312_DB_H */ +diff -wpruN --no-dereference '--exclude=*.orig' a~/py_db/mapfile-vers a/py_db/mapfile-vers +--- a~/py_db/mapfile-vers 1970-01-01 00:00:00 ++++ a/py_db/mapfile-vers 1970-01-01 00:00:00 +@@ -0,0 +1,39 @@ ++# ++# CDDL HEADER START ++# ++# The contents of this file are subject to the terms of the ++# Common Development and Distribution License (the "License"). ++# You may not use this file except in compliance with the License. ++# ++# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE ++# or http://www.opensolaris.org/os/licensing. ++# See the License for the specific language governing permissions ++# and limitations under the License. ++# ++# When distributing Covered Code, include this CDDL HEADER in each ++# file and include the License file at usr/src/OPENSOLARIS.LICENSE. ++# If applicable, add the following below this CDDL HEADER, with the ++# fields enclosed by brackets "[]" replaced with your own identifying ++# information: Portions Copyright [yyyy] [name of copyright owner] ++# ++# CDDL HEADER END ++# ++ ++# ++# Copyright (c) 2012, 2020, Oracle and/or its affiliates. ++# ++ ++SUNWprivate_1.1 { ++ global: ++ pydb_agent_create; ++ pydb_agent_destroy; ++ pydb_frame_iter_init; ++ pydb_get_frameinfo; ++ pydb_pc_frameinfo; ++ pydb_interp_iter_init; ++ pydb_thread_iter_init; ++ pydb_iter_fini; ++ pydb_iter_next; ++ local: ++ *; ++}; diff --git a/build/python313/patches/pyc-timestamp.patch b/build/python313/patches/pyc-timestamp.patch new file mode 100644 index 0000000000..6e2c39fdc5 --- /dev/null +++ b/build/python313/patches/pyc-timestamp.patch @@ -0,0 +1,26 @@ + +When packaging python modules along with their compiled source, we +deliberately set the timestamp attributes for both the source +and compiled file so that they are not recompiled the first time +root uses them (via pkg, for example). Doing so causes `pkg validate` +errors and slows down the initial invocation. + +However, the timestamp and size of the .py file is also embedded within +the .pyc header. This patch allows overriding that embedded timestamp +with a value of our choosing. See lib/functions.sh for how this is set. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/py_compile.py a/Lib/py_compile.py +--- a~/Lib/py_compile.py 1970-01-01 00:00:00 ++++ a/Lib/py_compile.py 1970-01-01 00:00:00 +@@ -159,6 +159,11 @@ def compile(file, cfile=None, dfile=None + pass + if invalidation_mode == PycInvalidationMode.TIMESTAMP: + source_stats = loader.path_stats(file) ++ if fpepoch := os.environ.get('FORCE_PYC_TIMESTAMP'): ++ try: ++ source_stats['mtime'] = int(fpepoch) ++ except ValueError: ++ raise ValueError("FORCE_PYC_TIMESTAMP is not a valid integer") + bytecode = importlib._bootstrap_external._code_to_timestamp_pyc( + code, source_stats['mtime'], source_stats['size']) + else: diff --git a/build/python313/patches/restore-dtrace.patch b/build/python313/patches/restore-dtrace.patch new file mode 100644 index 0000000000..d4a7bc28de --- /dev/null +++ b/build/python313/patches/restore-dtrace.patch @@ -0,0 +1,280 @@ +See https://github.com/python/cpython/issues/98894 + +Author: Bill Sommerfeld + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_dtrace.py a/Lib/test/test_dtrace.py +--- a~/Lib/test/test_dtrace.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_dtrace.py 1970-01-01 00:00:00 +@@ -126,7 +126,7 @@ class TraceTests: + def test_verify_call_opcodes(self): + """Ensure our call stack test hits all function call opcodes""" + +- opcodes = set(["CALL_FUNCTION", "CALL_FUNCTION_EX", "CALL_FUNCTION_KW"]) ++ opcodes = set(["CALL", "CALL_FUNCTION_EX"]) + + with open(abspath("call_stack.py")) as f: + code_string = f.read() +@@ -151,6 +151,7 @@ class TraceTests: + def test_gc(self): + self.run_case("gc") + ++ @unittest.skipIf(True, "not currently implemented") + def test_line(self): + self.run_case("line") + +@@ -183,6 +184,8 @@ class CheckDtraceProbes(unittest.TestCas + print(f"readelf version: {readelf_major_version}.{readelf_minor_version}") + else: + raise unittest.SkipTest("CPython must be configured with the --with-dtrace option.") ++ # These checks are only relevant with the SystemTap backend ++ SystemTapBackend().assert_usable() + + + @staticmethod +diff -wpruN --no-dereference '--exclude=*.orig' a~/Python/bytecodes.c a/Python/bytecodes.c +--- a~/Python/bytecodes.c 1970-01-01 00:00:00 ++++ a/Python/bytecodes.c 1970-01-01 00:00:00 +@@ -834,6 +834,7 @@ dummy_func( + SYNC_SP(); + _PyFrame_SetStackPointer(frame, stack_pointer); + assert(EMPTY()); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + // GH-99729: We need to unlink the frame *before* clearing it: + _PyInterpreterFrame *dying = frame; +@@ -856,6 +857,7 @@ dummy_func( + STACK_SHRINK(1); + assert(EMPTY()); + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + assert(frame != &entry_frame); + // GH-99729: We need to unlink the frame *before* clearing it: +@@ -880,6 +882,7 @@ dummy_func( + Py_INCREF(retval); + assert(EMPTY()); + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + assert(frame != &entry_frame); + // GH-99729: We need to unlink the frame *before* clearing it: +@@ -1077,6 +1080,7 @@ dummy_func( + assert(oparg == 0 || oparg == 1); + gen->gi_frame_state = FRAME_SUSPENDED + oparg; + _PyFrame_SetStackPointer(frame, stack_pointer - 1); ++ DTRACE_FUNCTION_EXIT(); + int err = _Py_call_instrumentation_arg( + tstate, PY_MONITORING_EVENT_PY_YIELD, + frame, this_instr, retval); +@@ -1108,6 +1112,7 @@ dummy_func( + gen->gi_frame_state = FRAME_SUSPENDED + oparg; + SYNC_SP(); + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + tstate->exc_info = gen->gi_exc_state.previous_item; + gen->gi_exc_state.previous_item = NULL; + _Py_LeaveRecursiveCallPy(tstate); +diff -wpruN --no-dereference '--exclude=*.orig' a~/Python/ceval.c a/Python/ceval.c +--- a~/Python/ceval.c 1970-01-01 00:00:00 ++++ a/Python/ceval.c 1970-01-01 00:00:00 +@@ -106,6 +106,70 @@ + } while (0) + #endif + ++static void ++dtrace_function_entry(_PyInterpreterFrame *frame) ++{ ++ const char *filename; ++ const char *funcname; ++ int lineno; ++ ++ PyCodeObject *code = _PyFrame_GetCode(frame); ++ filename = PyUnicode_AsUTF8(code->co_filename); ++ funcname = PyUnicode_AsUTF8(code->co_name); ++ lineno = PyUnstable_InterpreterFrame_GetLine(frame), ++ ++ PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno); ++} ++ ++static void ++dtrace_function_return(_PyInterpreterFrame *frame) ++{ ++ const char *filename; ++ const char *funcname; ++ int lineno; ++ ++ PyCodeObject *code = _PyFrame_GetCode(frame); ++ filename = PyUnicode_AsUTF8(code->co_filename); ++ funcname = PyUnicode_AsUTF8(code->co_name); ++ lineno = PyUnstable_InterpreterFrame_GetLine(frame), ++ ++ PyDTrace_FUNCTION_RETURN(filename, funcname, lineno); ++} ++ ++#if 0 ++/* DTrace equivalent of maybe_call_line_trace. */ ++static void ++maybe_dtrace_line(_PyInterpreterFrame *frame, ++ PyTraceInfo *trace_info, ++ int instr_prev) ++{ ++ const char *co_filename, *co_name; ++ ++ /* If the last instruction executed isn't in the current ++ instruction window, reset the window. ++ */ ++ initialize_trace_info(trace_info, frame); ++ int lastline = _PyCode_CheckLineNumber(instr_prev*sizeof(_Py_CODEUNIT), &trace_info->bounds); ++ int addr = _PyInterpreterFrame_LASTI(frame) * sizeof(_Py_CODEUNIT); ++ int line = _PyCode_CheckLineNumber(addr, &trace_info->bounds); ++ if (line != -1) { ++ /* Trace backward edges or first instruction of a new line */ ++ if (_PyInterpreterFrame_LASTI(frame) < instr_prev || ++ (line != lastline && addr == trace_info->bounds.ar_start)) ++ { ++ co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename); ++ if (!co_filename) { ++ co_filename = "?"; ++ } ++ co_name = PyUnicode_AsUTF8(frame->f_code->co_name); ++ if (!co_name) { ++ co_name = "?"; ++ } ++ PyDTrace_LINE(co_filename, co_name, line); ++ } ++ } ++} ++#endif + + #ifdef LLTRACE + static void +@@ -732,6 +796,7 @@ _PyEval_EvalFrameDefault(PyThreadState * + if (_Py_EnterRecursivePy(tstate)) { + goto exit_unwind; + } ++ DTRACE_FUNCTION_ENTRY(); + /* Because this avoids the RESUME, + * we need to update instrumentation */ + _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp); +@@ -757,6 +822,7 @@ start_frame: + } + + next_instr = frame->instr_ptr; ++ DTRACE_FUNCTION_ENTRY(); + resume_frame: + stack_pointer = _PyFrame_GetStackPointer(frame); + +@@ -893,6 +959,7 @@ exception_unwind: + } + assert(STACK_LEVEL() == 0); + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + monitor_unwind(tstate, frame, next_instr-1); + goto exit_unwind; + } +diff -wpruN --no-dereference '--exclude=*.orig' a~/Python/ceval_macros.h a/Python/ceval_macros.h +--- a~/Python/ceval_macros.h 1970-01-01 00:00:00 ++++ a/Python/ceval_macros.h 1970-01-01 00:00:00 +@@ -297,6 +297,11 @@ GETITEM(PyObject *v, Py_ssize_t i) { + #define CONSTS() _PyFrame_GetCode(frame)->co_consts + #define NAMES() _PyFrame_GetCode(frame)->co_names + ++#define DTRACE_FUNCTION_EXIT() \ ++ if (PyDTrace_FUNCTION_RETURN_ENABLED()) { \ ++ dtrace_function_return(frame); \ ++ } ++ + #define DTRACE_FUNCTION_ENTRY() \ + if (PyDTrace_FUNCTION_ENTRY_ENABLED()) { \ + dtrace_function_entry(frame); \ +diff -wpruN --no-dereference '--exclude=*.orig' a~/Python/generated_cases.c.h a/Python/generated_cases.c.h +--- a~/Python/generated_cases.c.h 1970-01-01 00:00:00 ++++ a/Python/generated_cases.c.h 1970-01-01 00:00:00 +@@ -996,6 +996,7 @@ + new_frame->previous = frame; + CALL_STAT_INC(inlined_py_calls); + frame = tstate->current_frame = new_frame; ++ DTRACE_FUNCTION_ENTRY(); + tstate->py_recursion_remaining--; + LOAD_SP(); + LOAD_IP(0); +@@ -1087,6 +1088,7 @@ + new_frame->previous = frame; + CALL_STAT_INC(inlined_py_calls); + frame = tstate->current_frame = new_frame; ++ DTRACE_FUNCTION_ENTRY(); + tstate->py_recursion_remaining--; + LOAD_SP(); + LOAD_IP(0); +@@ -1921,6 +1923,7 @@ + new_frame->previous = frame; + CALL_STAT_INC(inlined_py_calls); + frame = tstate->current_frame = new_frame; ++ DTRACE_FUNCTION_ENTRY(); + tstate->py_recursion_remaining--; + LOAD_SP(); + LOAD_IP(0); +@@ -1993,6 +1996,7 @@ + new_frame->previous = frame; + CALL_STAT_INC(inlined_py_calls); + frame = tstate->current_frame = new_frame; ++ DTRACE_FUNCTION_ENTRY(); + tstate->py_recursion_remaining--; + LOAD_SP(); + LOAD_IP(0); +@@ -2846,6 +2850,7 @@ + new_frame->previous = frame; + CALL_STAT_INC(inlined_py_calls); + frame = tstate->current_frame = new_frame; ++ DTRACE_FUNCTION_ENTRY(); + tstate->py_recursion_remaining--; + LOAD_SP(); + LOAD_IP(0); +@@ -3497,6 +3502,7 @@ + Py_INCREF(retval); + assert(EMPTY()); + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + assert(frame != &entry_frame); + // GH-99729: We need to unlink the frame *before* clearing it: +@@ -3522,6 +3528,7 @@ + STACK_SHRINK(1); + assert(EMPTY()); + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + assert(frame != &entry_frame); + // GH-99729: We need to unlink the frame *before* clearing it: +@@ -3547,6 +3554,7 @@ + assert(oparg == 0 || oparg == 1); + gen->gi_frame_state = FRAME_SUSPENDED + oparg; + _PyFrame_SetStackPointer(frame, stack_pointer - 1); ++ DTRACE_FUNCTION_EXIT(); + int err = _Py_call_instrumentation_arg( + tstate, PY_MONITORING_EVENT_PY_YIELD, + frame, this_instr, retval); +@@ -5197,6 +5205,7 @@ + #endif + _PyFrame_SetStackPointer(frame, stack_pointer); + assert(EMPTY()); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + // GH-99729: We need to unlink the frame *before* clearing it: + _PyInterpreterFrame *dying = frame; +@@ -5254,6 +5263,7 @@ + stack_pointer += -1; + _PyFrame_SetStackPointer(frame, stack_pointer); + assert(EMPTY()); ++ DTRACE_FUNCTION_EXIT(); + _Py_LeaveRecursiveCallPy(tstate); + // GH-99729: We need to unlink the frame *before* clearing it: + _PyInterpreterFrame *dying = frame; +@@ -6230,6 +6240,7 @@ + gen->gi_frame_state = FRAME_SUSPENDED + oparg; + stack_pointer += -1; + _PyFrame_SetStackPointer(frame, stack_pointer); ++ DTRACE_FUNCTION_EXIT(); + tstate->exc_info = gen->gi_exc_state.previous_item; + gen->gi_exc_state.previous_item = NULL; + _Py_LeaveRecursiveCallPy(tstate); diff --git a/build/python313/patches/revert-makedirs.patch b/build/python313/patches/revert-makedirs.patch new file mode 100644 index 0000000000..73e741ef5b --- /dev/null +++ b/build/python313/patches/revert-makedirs.patch @@ -0,0 +1,46 @@ +This patch reverts changes made to `os.makedirs()` in Python 3.7. + +Prior to Python 3.7, mode argument also affected newly-created +intermediate-level directories, and that is not the case anymore [1]. The +reason was that with mode preventing directory creating, the function fails +after it creates the first intermediate-level directory. + +Since there is Solaris code that depends on the old behavior, this patch brings +it back. Note that we are not the only ones affected by this; upstream is +already considering reverting this change [2]. + +For reference: +[1] https://bugs.python.org/issue19930 +[2] https://bugs.python.org/issue42367 + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/os.py a/Lib/os.py +--- a~/Lib/os.py 1970-01-01 00:00:00 ++++ a/Lib/os.py 1970-01-01 00:00:00 +@@ -214,7 +214,7 @@ def makedirs(name, mode=0o777, exist_ok= + head, tail = path.split(head) + if head and tail and not path.exists(head): + try: +- makedirs(head, exist_ok=exist_ok) ++ makedirs(head, mode, exist_ok) + except FileExistsError: + # Defeats race condition when another thread created the path + pass +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_os.py a/Lib/test/test_os.py +--- a~/Lib/test/test_os.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_os.py 1970-01-01 00:00:00 +@@ -1764,12 +1764,12 @@ class MakedirTests(unittest.TestCase): + base = os_helper.TESTFN + parent = os.path.join(base, 'dir1') + path = os.path.join(parent, 'dir2') +- os.makedirs(path, 0o555) ++ os.makedirs(path, 0o755) + self.assertTrue(os.path.exists(path)) + self.assertTrue(os.path.isdir(path)) + if os.name != 'nt': +- self.assertEqual(os.stat(path).st_mode & 0o777, 0o555) +- self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775) ++ self.assertEqual(os.stat(path).st_mode & 0o777, 0o755) ++ self.assertEqual(os.stat(parent).st_mode & 0o777, 0o755) + + @unittest.skipIf( + support.is_emscripten or support.is_wasi, diff --git a/build/python313/patches/series b/build/python313/patches/series new file mode 100644 index 0000000000..e67f8c4643 --- /dev/null +++ b/build/python313/patches/series @@ -0,0 +1,41 @@ +illumos.patch +autoconf-macros.patch +vendor-packages.patch +dont-use-ccs.patch +default-lib-path.patch +disable_epoll.patch +strxfrm-fix.patch +encoding-alias.patch +locale-encoding.patch +pyc-timestamp.patch +asyncio-watcher.patch +#XXX py_db.patch +restore-dtrace.patch +# +# Additional modules +module-ucred.patch +module-dlpi.patch +module-priv-rbac.patch +# +# Module fixes +mod-posix-sched_priority.patch +mod-shutil-sendfile.patch +termios-set-before-get.patch +# +# Test related +test-os.patch +test-email.patch +test-filecomments.patch +test-freeze.patch +test-metadata.patch +test-opts.patch +test-pkgutil.patch +test-processgroup.patch +test-tarfile.patch +test-zipfile.patch +test-re.patch +revert-makedirs.patch +# +# Do not add ustack.patch to this file, it is used to build the debug +# variant of python, see build.sh +gethostbyname.patch diff --git a/build/python313/patches/strxfrm-fix.patch b/build/python313/patches/strxfrm-fix.patch new file mode 100644 index 0000000000..83f15f2eca --- /dev/null +++ b/build/python313/patches/strxfrm-fix.patch @@ -0,0 +1,46 @@ +This patch fixes limitation in python which expects strxfrm function to return +string with characters with values lower than 10ffff. This is know issue: + +https://bugs.python.org/issue16258 + +This is not for upstream as the idea is from the bug itself and was rejected +for use on all platforms. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/_localemodule.c a/Modules/_localemodule.c +--- a~/Modules/_localemodule.c 1970-01-01 00:00:00 ++++ a/Modules/_localemodule.c 1970-01-01 00:00:00 +@@ -384,7 +384,7 @@ _locale_strxfrm_impl(PyObject *module, P + /*[clinic end generated code: output=3081866ebffc01af input=1378bbe6a88b4780]*/ + { + Py_ssize_t n1; +- wchar_t *s = NULL, *buf = NULL; ++ wchar_t *s = NULL, *buf = NULL, *solbuf = NULL; + size_t n2; + PyObject *result = NULL; + +@@ -425,8 +425,24 @@ _locale_strxfrm_impl(PyObject *module, P + goto exit; + } + } +- result = PyUnicode_FromWideChar(buf, n2); ++ ++ /* Split each character in resulting wide string in two ++ parts in order to prevent Python ValueErrors on Solaris. */ ++ solbuf = PyMem_New(wchar_t, (n2*2) + 1); ++ if (!solbuf) { ++ PyErr_NoMemory(); ++ goto exit; ++ } ++ unsigned int i, j; ++ for (i = 0, j = 0; i < n2; i ++, j+= 2) { ++ solbuf[j] = 0x10000 + (buf[i] >> 16); ++ solbuf[j+1] = buf[i] & 0xffff; ++ } ++ solbuf[j] = 0; ++ ++ result = PyUnicode_FromWideChar(solbuf, n2*2); + exit: ++ PyMem_Free(solbuf); + PyMem_Free(buf); + PyMem_Free(s); + return result; diff --git a/build/python313/patches/termios-set-before-get.patch b/build/python313/patches/termios-set-before-get.patch new file mode 100644 index 0000000000..500f8c0511 --- /dev/null +++ b/build/python313/patches/termios-set-before-get.patch @@ -0,0 +1,42 @@ +See https://github.com/python/cpython/issues/115189 + +Author: Bill Sommerfeld + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_termios.py a/Lib/test/test_termios.py +--- a~/Lib/test/test_termios.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_termios.py 1970-01-01 00:00:00 +@@ -13,6 +13,16 @@ class TestFunctions(unittest.TestCase): + + def setUp(self): + master_fd, self.fd = os.openpty() ++ # some systems will not let you get the window size unless it's been ++ # set first, so try to set it if we can't get it ++ try: ++ termios.tcgetwinsize(self.fd) ++ except termios.error: ++ try: ++ termios.tcsetwinsize(self.fd, (24, 80)) ++ except termios.error: ++ pass ++ + self.addCleanup(os.close, master_fd) + self.stream = self.enterContext(open(self.fd, 'wb', buffering=0)) + tmp = self.enterContext(tempfile.TemporaryFile(mode='wb', buffering=0)) +diff -wpruN --no-dereference '--exclude=*.orig' a~/Modules/termios.c a/Modules/termios.c +--- a~/Modules/termios.c 1970-01-01 00:00:00 ++++ a/Modules/termios.c 1970-01-01 00:00:00 +@@ -518,9 +518,12 @@ termios_tcsetwinsize_impl(PyObject *modu + #if defined(TIOCGWINSZ) && defined(TIOCSWINSZ) + struct winsize w; + /* Get the old winsize because it might have +- more fields such as xpixel, ypixel. */ ++ more fields such as xpixel, ypixel, but keep going and ++ try to set it even if this fails since on some systems ++ on a new pty you can't get the winsize until it's been ++ set once. */ + if (ioctl(fd, TIOCGWINSZ, &w) == -1) { +- return PyErr_SetFromErrno(state->TermiosError); ++ memset(&w, 0, sizeof(w)); + } + + w.ws_row = (unsigned short) winsz_0; diff --git a/build/python313/patches/test-email.patch b/build/python313/patches/test-email.patch new file mode 100644 index 0000000000..9287804908 --- /dev/null +++ b/build/python313/patches/test-email.patch @@ -0,0 +1,12 @@ +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_email/test_utils.py a/Lib/test/test_email/test_utils.py +--- a~/Lib/test/test_email/test_utils.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_email/test_utils.py 1970-01-01 00:00:00 +@@ -143,6 +143,8 @@ class LocaltimeTests(unittest.TestCase): + t2 = utils.localtime(t0.replace(tzinfo=None)) + self.assertEqual(t1, t2) + ++ @unittest.skipIf(sys.platform.startswith("sunos"), ++ "The Kyiv database on SunOS puts 1984 in EET") + @test.support.run_with_tz('Europe/Kyiv') + def test_variable_tzname(self): + t0 = datetime.datetime(1984, 1, 1, tzinfo=datetime.timezone.utc) diff --git a/build/python313/patches/test-filecomments.patch b/build/python313/patches/test-filecomments.patch new file mode 100644 index 0000000000..ab6828efd7 --- /dev/null +++ b/build/python313/patches/test-filecomments.patch @@ -0,0 +1,14 @@ + +Support comments in the test ignore file (see ../files/test.exclude) + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/libregrtest/cmdline.py a/Lib/test/libregrtest/cmdline.py +--- a~/Lib/test/libregrtest/cmdline.py 1970-01-01 00:00:00 ++++ a/Lib/test/libregrtest/cmdline.py 1970-01-01 00:00:00 +@@ -196,6 +196,7 @@ class FromFileFilterAction(argparse.Acti + items = getattr(namespace, self.dest) + with open(value, encoding='utf-8') as fp: + for line in fp: ++ if line.startswith('#'): continue + items.append((line.strip(), self.const)) + + diff --git a/build/python313/patches/test-freeze.patch b/build/python313/patches/test-freeze.patch new file mode 100644 index 0000000000..0105f1724f --- /dev/null +++ b/build/python313/patches/test-freeze.patch @@ -0,0 +1,36 @@ + +This patch: + + - updates the test to run GNU make; + - Strips all configure arguments of the form `--XXXdir=YYY` from the configure + lines used to build the new perl tree, since these override the temporary + --prefix being used for the test. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Tools/freeze/test/freeze.py a/Tools/freeze/test/freeze.py +--- a~/Tools/freeze/test/freeze.py 1970-01-01 00:00:00 ++++ a/Tools/freeze/test/freeze.py 1970-01-01 00:00:00 +@@ -6,6 +6,7 @@ import subprocess + import sysconfig + from test import support + ++import re + + def get_python_source_dir(): + src_dir = sysconfig.get_config_var('abs_srcdir') +@@ -18,7 +18,7 @@ TESTS_DIR = os.path.dirname(__file__) + TOOL_ROOT = os.path.dirname(TESTS_DIR) + SRCDIR = get_python_source_dir() + +-MAKE = shutil.which('make') ++MAKE = shutil.which('gmake') + FREEZE = os.path.join(TOOL_ROOT, 'freeze.py') + OUTDIR = os.path.join(TESTS_DIR, 'outdir') + +@@ -129,6 +129,7 @@ def prepare(script=None, outdir=None): + print(f'configuring python in {builddir}...') + config_args = shlex.split(sysconfig.get_config_var('CONFIG_ARGS') or '') + cmd = [os.path.join(srcdir, 'configure'), *config_args] ++ cmd = [c for c in cmd if not re.search(r'^--.*dir=', c)] + ensure_opt(cmd, 'cache-file', os.path.join(outdir, 'python-config.cache')) + prefix = os.path.join(outdir, 'python-installation') + ensure_opt(cmd, 'prefix', prefix) diff --git a/build/python313/patches/test-metadata.patch b/build/python313/patches/test-metadata.patch new file mode 100644 index 0000000000..3cbc39970c --- /dev/null +++ b/build/python313/patches/test-metadata.patch @@ -0,0 +1,16 @@ + +The importlib test checks that there is no package called 'pkg', which is not +true on OmniOS. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_importlib/metadata/test_api.py a/Lib/test/test_importlib/metadata/test_api.py +--- a~/Lib/test/test_importlib/metadata/test_api.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_importlib/metadata/test_api.py 1970-01-01 00:00:00 +@@ -59,7 +59,7 @@ class APITests( + assert distribution(name).metadata['Name'] == 'pkg.dot' + + def test_prefix_not_matched(self): +- prefixes = 'p', 'pkg', 'pkg.' ++ prefixes = 'p', 'qkg', 'qkg.' + for prefix in prefixes: + with self.subTest(prefix): + with self.assertRaises(PackageNotFoundError): diff --git a/build/python313/patches/test-opts.patch b/build/python313/patches/test-opts.patch new file mode 100644 index 0000000000..36c7fb018c --- /dev/null +++ b/build/python313/patches/test-opts.patch @@ -0,0 +1,20 @@ + +When running the tests as part of the build, we want to run them in a +consistent order and not automatically re-launch them in verbose mode +when a failure occurs. + + -r Randomize test order + -w Re-run failed tests in verbose mode + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_regrtest.py a/Lib/test/test_regrtest.py +--- a~/Lib/test/test_regrtest.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_regrtest.py 1970-01-01 00:00:00 +@@ -804,7 +804,7 @@ class ProgramsTestCase(BaseTestCase): + self.tests = [self.create_test() for index in range(self.NTEST)] + + self.python_args = ['-Wd', '-E', '-bb'] +- self.regrtest_args = ['-uall', '-rwW', ++ self.regrtest_args = ['-uall', '-W', + '--testdir=%s' % self.tmptestdir] + self.regrtest_args.extend(('--timeout', '3600', '-j4')) + if sys.platform == 'win32': diff --git a/build/python313/patches/test-os.patch b/build/python313/patches/test-os.patch new file mode 100644 index 0000000000..91f37b957c --- /dev/null +++ b/build/python313/patches/test-os.patch @@ -0,0 +1,15 @@ + +Prior to being able to call `isatty()` or `ttyname()` on a new +subsidiary pty, the "ptem" module must be pushed there. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_os.py a/Lib/test/test_os.py +--- a~/Lib/test/test_os.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_os.py 1970-01-01 00:00:00 +@@ -4716,6 +4716,7 @@ class PseudoterminalTests(unittest.TestC + son_path = os.ptsname(mother_fd) + son_fd = os.open(son_path, os.O_RDWR|os.O_NOCTTY) + self.addCleanup(os.close, son_fd) ++ fcntl.ioctl(son_fd, fcntl.I_PUSH, "ptem") + self.assertEqual(os.ptsname(mother_fd), os.ttyname(son_fd)) + + @unittest.skipUnless(hasattr(os, 'spawnl'), "need os.spawnl()") diff --git a/build/python313/patches/test-pkgutil.patch b/build/python313/patches/test-pkgutil.patch new file mode 100644 index 0000000000..1d796d7037 --- /dev/null +++ b/build/python313/patches/test-pkgutil.patch @@ -0,0 +1,43 @@ +Python does not expect the build system to already have a 'pkg' module, and +it may well have if it's an IPS image. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_pkgutil.py a/Lib/test/test_pkgutil.py +--- a~/Lib/test/test_pkgutil.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_pkgutil.py 1970-01-01 00:00:00 +@@ -582,22 +582,22 @@ class NestedNamespacePackageTest(unittes + pkgutil_boilerplate = ( + 'import pkgutil; ' + '__path__ = pkgutil.extend_path(__path__, __name__)') +- self.create_module('a.pkg.__init__', pkgutil_boilerplate) +- self.create_module('b.pkg.__init__', pkgutil_boilerplate) +- self.create_module('a.pkg.subpkg.__init__', pkgutil_boilerplate) +- self.create_module('b.pkg.subpkg.__init__', pkgutil_boilerplate) +- self.create_module('a.pkg.subpkg.c', 'c = 1') +- self.create_module('b.pkg.subpkg.d', 'd = 2') ++ self.create_module('a._pkg.__init__', pkgutil_boilerplate) ++ self.create_module('b._pkg.__init__', pkgutil_boilerplate) ++ self.create_module('a._pkg.subpkg.__init__', pkgutil_boilerplate) ++ self.create_module('b._pkg.subpkg.__init__', pkgutil_boilerplate) ++ self.create_module('a._pkg.subpkg.c', 'c = 1') ++ self.create_module('b._pkg.subpkg.d', 'd = 2') + sys.path.insert(0, os.path.join(self.basedir, 'a')) + sys.path.insert(0, os.path.join(self.basedir, 'b')) +- import pkg +- self.addCleanup(unload, 'pkg') +- self.assertEqual(len(pkg.__path__), 2) +- import pkg.subpkg +- self.addCleanup(unload, 'pkg.subpkg') +- self.assertEqual(len(pkg.subpkg.__path__), 2) +- from pkg.subpkg.c import c +- from pkg.subpkg.d import d ++ import _pkg ++ self.addCleanup(unload, '_pkg') ++ self.assertEqual(len(_pkg.__path__), 2) ++ import _pkg.subpkg ++ self.addCleanup(unload, '_pkg.subpkg') ++ self.assertEqual(len(_pkg.subpkg.__path__), 2) ++ from _pkg.subpkg.c import c ++ from _pkg.subpkg.d import d + self.assertEqual(c, 1) + self.assertEqual(d, 2) + diff --git a/build/python313/patches/test-processgroup.patch b/build/python313/patches/test-processgroup.patch new file mode 100644 index 0000000000..0994a45f7e --- /dev/null +++ b/build/python313/patches/test-processgroup.patch @@ -0,0 +1,18 @@ + +Running some sub-tests in process groups causes tests to hang - the reason it +not yet known. +Disable the use of process groups for now. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/libregrtest/worker.py a/Lib/test/libregrtest/worker.py +--- a~/Lib/test/libregrtest/worker.py 1970-01-01 00:00:00 ++++ a/Lib/test/libregrtest/worker.py 1970-01-01 00:00:00 +@@ -13,7 +13,8 @@ from .utils import ( + get_temp_dir, get_work_dir, exit_timeout) + + +-USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg")) ++USE_PROCESS_GROUP = (hasattr(os, "setsid") and hasattr(os, "killpg") ++ and not sys.platform.startswith("sunos")) + + + def create_worker_process(runtests: WorkerRunTests, output_fd: int, diff --git a/build/python313/patches/test-re.patch b/build/python313/patches/test-re.patch new file mode 100644 index 0000000000..dcd5aba97a --- /dev/null +++ b/build/python313/patches/test-re.patch @@ -0,0 +1,21 @@ +XXX TBD + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_re.py a/Lib/test/test_re.py +--- a~/Lib/test/test_re.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_re.py 1970-01-01 00:00:00 +@@ -2180,6 +2180,7 @@ class ReTests(unittest.TestCase): + is_emscripten or is_wasi, + "musl libc issue on Emscripten/WASI, bpo-46390" + ) ++ @unittest.skipIf(sys.platform.startswith("sunos"), "locale") + def test_locale_caching(self): + # Issue #22410 + oldlocale = locale.setlocale(locale.LC_CTYPE) +@@ -2220,6 +2221,7 @@ class ReTests(unittest.TestCase): + is_emscripten or is_wasi, + "musl libc issue on Emscripten/WASI, bpo-46390" + ) ++ @unittest.skipIf(sys.platform.startswith("sunos"), "locale") + def test_locale_compiled(self): + oldlocale = locale.setlocale(locale.LC_CTYPE) + self.addCleanup(locale.setlocale, locale.LC_CTYPE, oldlocale) diff --git a/build/python313/patches/test-tarfile.patch b/build/python313/patches/test-tarfile.patch new file mode 100644 index 0000000000..0413e1b095 --- /dev/null +++ b/build/python313/patches/test-tarfile.patch @@ -0,0 +1,20 @@ + +The resolution of file timestamps is different in ZFS to tmpfs, so the +comparison fails with an error like: + + AssertionError: 1686679063.0 not greater than or equal to 1686679063.8400018 + +Convert both timestamps to integer before comparing. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_tarfile.py a/Lib/test/test_tarfile.py +--- a~/Lib/test/test_tarfile.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_tarfile.py 1970-01-01 00:00:00 +@@ -3267,7 +3267,7 @@ class NoneInfoExtractTests(ReadTest): + if not path.is_symlink(): + raise + else: +- self.assertGreaterEqual(path.stat().st_mtime, now) ++ self.assertGreaterEqual(int(path.stat().st_mtime), int(now)) + + def test_extractall_none_mode(self): + # modes of directories and regular files should match the mode diff --git a/build/python313/patches/test-zipfile.patch b/build/python313/patches/test-zipfile.patch new file mode 100644 index 0000000000..828f558ebd --- /dev/null +++ b/build/python313/patches/test-zipfile.patch @@ -0,0 +1,18 @@ + +This test tries to set a file date to 2108-12-30. +On illumos, the os.utime() call raises: + OSError: [Errno 79] Value too large for defined data type +when the test expects an overflow error. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/test/test_zipfile/test_core.py a/Lib/test/test_zipfile/test_core.py +--- a~/Lib/test/test_zipfile/test_core.py 1970-01-01 00:00:00 ++++ a/Lib/test/test_zipfile/test_core.py 1970-01-01 00:00:00 +@@ -641,7 +641,7 @@ class StoredTestsWithSourceFile(Abstract + self.skipTest(f'time.localtime({ts}) raises OverflowError') + try: + os.utime(TESTFN, (ts, ts)) +- except OverflowError: ++ except (OverflowError, OSError): + self.skipTest('Host fs cannot set timestamp to required value.') + + mtime_ns = os.stat(TESTFN).st_mtime_ns diff --git a/build/python313/patches/ustack.patch b/build/python313/patches/ustack.patch new file mode 100644 index 0000000000..b1f177601b --- /dev/null +++ b/build/python313/patches/ustack.patch @@ -0,0 +1,668 @@ +From 91e13cc2104fc0dae396d59341ee8f23de2c3db5 Mon Sep 17 00:00:00 2001 +From: Andy Fiddaman +Date: Sat, 2 Mar 2024 17:38:00 +0000 +Subject: [PATCH] Python: Add dtrace ustack helper + +Python acquired native dtrace support in version 3.6, but without the +ustack helper that annotates stack traces with information about the +python function being called. + +This partially restores the ustack helper and is based on Sun's original dtrace +patches. + +Some more information on the original work can be found at: +https://movementarian.org/blog/posts/2007-05-24-python-and-dtrace-in-build-65 +--- + Include/dtrace.cpp.sh | 33 ++++ + Include/pydtrace.d | 149 ++++++++++++++++++ + Include/pydtrace.h | 12 +- + Include/pydtrace_offsets.c | 34 ++++ + Include/pydtrace_symbols.sh | 15 ++ + Lib/test/dtracedata/jstack.d | 18 +++ + Lib/test/dtracedata/jstack.d.expected | 21 +++ + Lib/test/dtracedata/jstack.py | 25 +++ + Lib/test/dtracedata/unicode-jstack.d | 18 +++ + Lib/test/dtracedata/unicode-jstack.d.expected | 18 +++ + Lib/test/dtracedata/unicode-jstack.py | 15 ++ + Lib/test/test_dtrace.py | 31 ++++ + Makefile.pre.in | 35 +++- + Python/ceval.c | 29 ++++ + Python/import.c | 4 +- + Python/sysmodule.c | 2 +- + 16 files changed, 447 insertions(+), 12 deletions(-) + create mode 100755 Include/dtrace.cpp.sh + create mode 100644 Include/pydtrace_offsets.c + create mode 100755 Include/pydtrace_symbols.sh + create mode 100644 Lib/test/dtracedata/jstack.d + create mode 100644 Lib/test/dtracedata/jstack.d.expected + create mode 100644 Lib/test/dtracedata/jstack.py + create mode 100644 Lib/test/dtracedata/unicode-jstack.d + create mode 100644 Lib/test/dtracedata/unicode-jstack.d.expected + create mode 100644 Lib/test/dtracedata/unicode-jstack.py + +diff --git a/Include/dtrace.cpp.sh b/Include/dtrace.cpp.sh +new file mode 100755 +index 0000000000..9a3fc3eed2 +--- /dev/null ++++ b/Include/dtrace.cpp.sh +@@ -0,0 +1,33 @@ ++#!/bin/bash ++ ++# A cpp wrapper used by the dtrace compiler that strips //-style comments ++# and static inline functions ++ ++op=${@: -1} ++args=${@:1:$#-1} ++ ++# Our native cpp cannot cope with this, and we still need to remove some ++# pieces to keep the dtrace compiler happy. ++: "${DTRACE_CPP:=/opt/gcc-10/bin/cpp}" ++ ++$DTRACE_CPP $args \ ++ -D'__attribute__(x)=' \ ++ -D'__alignof__(x)=' \ ++ -D'__aligned(x)=' \ ++ -D__builtin_va_list='void *' \ ++ -D_Bool=char \ ++ -D_Noreturn= \ ++ -Dstring=_string \ ++ -Dcounter=_counter \ ++ | sed ' ++ s^//.*^^ ++ /^.*static inline .*/,/^}/d ++ /^.*static inline *$/,/^}/d ++ /\* *self\>/s/self/_&/ ++ /ob_refcnt_split/,/};/ { ++ s/};/} _u;/ ++ } ++ /__gnuc_va_list va_list\;/s/va_list;/_x_&/ ++ /^$/d ++' | tee dtrace.out >> $op ++ +diff --git a/Include/pydtrace.d b/Include/pydtrace.d +index 5e6a626b01..b45f5bb8c0 100644 +--- a/Include/pydtrace.d ++++ b/Include/pydtrace.d +@@ -20,3 +20,152 @@ provider python { + #pragma D attributes Evolving/Evolving/Common provider python function + #pragma D attributes Evolving/Evolving/Common provider python name + #pragma D attributes Evolving/Evolving/Common provider python args ++ ++#ifdef PYDTRACE_STACK_HELPER ++/* ++ * Python ustack helper. This relies on the first argument (PyFrame *) being ++ * on the stack; see Python/ceval.c for the contortions we go through to ensure ++ * this is the case. ++ * ++ * On x86, the PyFrame * is two slots up from the frame pointer. ++ * ++ * Some details about this in "Python and DTrace in build 65": ++ * https://movementarian.org/blog/posts/2007-05-24-python-and-dtrace-in-build-65 ++ */ ++ ++#include "pyconfig.h" ++#undef _POSIX_PTHREAD_SEMANTICS ++ ++#include ++#include ++ ++#define Py_EXPORTS_H ++#define Py_IMPORTED_SYMBOL ++#define Py_EXPORTED_SYMBOL ++#define Py_LOCAL_SYMBOL ++ ++#include "Python.h" ++#include "internal/pycore_frame.h" ++ ++#include "pydtrace_offsets.h" ++#include "pydtrace_symbols.h" ++ ++#define startframe _PyEval_EvalFrameDefaultReal ++#define endframe PYDTRACE_AFTER__PyEval_EvalFrameDefaultReal ++ ++extern uintptr_t startframe; ++extern uintptr_t endframe; ++ ++#define at_evalframe(addr) \ ++ ((uintptr_t)addr >= ((uintptr_t)&``startframe) && \ ++ (uintptr_t)addr < ((uintptr_t)&``endframe)) ++ ++#define frame_ptr_addr ((uintptr_t)arg1 + sizeof(uintptr_t) * 2) ++#define copyin_obj(addr, obj) ((obj *)copyin((uintptr_t)(addr), sizeof(obj))) ++ ++/* ++ * Check if the string is ASCII. Don't use bitfields, because the ++ * packing in GCC and D are different. BEWARE!!!. ++ */ ++#define pystr_isascii(addr) \ ++ ((*(((char *)addr) + PYDTRACE_ASCII_OFFSET)) & PYDTRACE_ASCII_MASK) ++#define pystr_len(addr) \ ++ (pystr_isascii(addr) ? (addr)->_base.length : \ ++ *(Py_ssize_t *)(((char *)(addr)) + PYDTRACE_UTF8_LENGTH_OFFSET)) ++#define pystr_addr(addr, addr2) \ ++ (pystr_isascii(addr) ? \ ++ (char *)(((char *)(addr2)) + PYDTRACE_PyASCIIObject_SIZE) : \ ++ (char *)*(uintptr_t *)(((char *)(addr)) + PYDTRACE_UTF8_OFFSET)) ++ ++#define add_digit(nr, div) (((nr) / div) ? \ ++ (this->result[this->pos++] = '0' + (((nr) / div) % 10)) : \ ++ (this->result[this->pos] = '\0')) ++#define add_char(c) \ ++ (this->result[this->pos++] = c) ++#define add_hex(d) \ ++ add_char((d) < 10 ? '0' + (d) : 'a' - 10 + (d)) ++ ++#define add_number(i) \ ++ add_digit((i), 100000); \ ++ add_digit((i), 10000); \ ++ add_digit((i), 1000); \ ++ add_digit((i), 100); \ ++ add_digit((i), 10); \ ++ add_digit((i), 1) ++ ++#define add_pointer32(p) \ ++ add_hex((p >> 28) & 0xf); \ ++ add_hex((p >> 24) & 0xf); \ ++ add_hex((p >> 20) & 0xf); \ ++ add_hex((p >> 16) & 0xf); \ ++ add_hex((p >> 12) & 0xf); \ ++ add_hex((p >> 8) & 0xf); \ ++ add_hex((p >> 4) & 0xf); \ ++ add_hex((p) & 0xf) ++ ++#define add_pointer(p) \ ++ add_pointer32(p >> 32); \ ++ add_pointer32(p) ++ ++dtrace:helper:ustack: ++{ ++ this->framep = *(uintptr_t *)copyin(frame_ptr_addr, sizeof(uintptr_t)); ++ this->frameo = copyin_obj(this->framep, struct _PyInterpreterFrame); ++ ++ /* ++ * Unfortunately for this ustack helper, python manages its own frames ++ * and in order to print them all we would have to walk the list at ++ * this->frameo->previous. We don't have a way of doing that yet - some ++ * inventive attempts have failed due to the lack of 'self' in this ++ * context - so we make do with printing the most recent stack frame. ++ */ ++ ++ this->codep = this->frameo->f_code; ++ this->codeo = copyin_obj(this->codep, PyCodeObject); ++ ++ this->filenameo = copyin_obj(this->codeo->co_filename, ++ PyCompactUnicodeObject); ++ this->nameo = copyin_obj(this->codeo->co_name, PyCompactUnicodeObject); ++ this->len_filename = pystr_len(this->filenameo); ++ this->len_name = pystr_len(this->nameo); ++ ++#if 0 ++ /* Line number determination still needs work */ ++ this->addr = this->frameo->prev_instr; ++ this->line = copyin_obj( ++ ((int32_t *)this->codeo->co_linetable)[(int32_t)this->addr], ++ int32_t); ++ this->lineno = *this->line; ++#else ++ this->lineno = 0; ++#endif ++ ++ this->len = 1 + this->len_filename + 1 + 5 + 2 + this->len_name + 1 + 1; ++ ++ this->result = (char *)alloca(this->len); ++ this->pos = 0; ++ add_char('@'); ++ ++ copyinto((uintptr_t)pystr_addr(this->filenameo, ++ this->codeo->co_filename), this->len_filename, ++ this->result + this->pos); ++ this->pos += this->len_filename; ++ ++ add_char(':'); ++ add_number(this->lineno); ++ add_char(' '); ++ add_char('('); ++ ++ copyinto((uintptr_t)pystr_addr(this->nameo, ++ this->codeo->co_name), this->len_name, ++ this->result + this->pos); ++ this->pos += this->len_name; ++ ++ add_char(')'); ++ this->result[this->pos] = '\0'; ++ ++ stringof(this->result) ++} ++ ++#endif /* PYDTRACE_STACK_HELPER */ ++ +diff --git a/Include/pydtrace.h b/Include/pydtrace.h +index e197d36694..35f01f7ddc 100644 +--- a/Include/pydtrace.h ++++ b/Include/pydtrace.h +@@ -25,18 +25,18 @@ extern "C" { + + /* Without DTrace, compile to nothing. */ + +-static inline void PyDTrace_LINE(const char *arg0, const char *arg1, int arg2) {} +-static inline void PyDTrace_FUNCTION_ENTRY(const char *arg0, const char *arg1, int arg2) {} +-static inline void PyDTrace_FUNCTION_RETURN(const char *arg0, const char *arg1, int arg2) {} ++static inline void PyDTrace_LINE(char *arg0, char *arg1, int arg2) {} ++static inline void PyDTrace_FUNCTION_ENTRY(char *arg0, char *arg1, int arg2) {} ++static inline void PyDTrace_FUNCTION_RETURN(char *arg0, char *arg1, int arg2) {} + static inline void PyDTrace_GC_START(int arg0) {} + static inline void PyDTrace_GC_DONE(Py_ssize_t arg0) {} + static inline void PyDTrace_INSTANCE_NEW_START(int arg0) {} + static inline void PyDTrace_INSTANCE_NEW_DONE(int arg0) {} + static inline void PyDTrace_INSTANCE_DELETE_START(int arg0) {} + static inline void PyDTrace_INSTANCE_DELETE_DONE(int arg0) {} +-static inline void PyDTrace_IMPORT_FIND_LOAD_START(const char *arg0) {} +-static inline void PyDTrace_IMPORT_FIND_LOAD_DONE(const char *arg0, int arg1) {} +-static inline void PyDTrace_AUDIT(const char *arg0, void *arg1) {} ++static inline void PyDTrace_IMPORT_FIND_LOAD_START(char *arg0) {} ++static inline void PyDTrace_IMPORT_FIND_LOAD_DONE(char *arg0, int arg1) {} ++static inline void PyDTrace_AUDIT(char *arg0, void *arg1) {} + + static inline int PyDTrace_LINE_ENABLED(void) { return 0; } + static inline int PyDTrace_FUNCTION_ENTRY_ENABLED(void) { return 0; } +diff --git a/Include/pydtrace_offsets.c b/Include/pydtrace_offsets.c +new file mode 100644 +index 0000000000..8cad556c84 +--- /dev/null ++++ b/Include/pydtrace_offsets.c +@@ -0,0 +1,34 @@ ++#include "Python.h" ++#include "unicodeobject.h" ++#include ++#include ++ ++int ++main(int argc, const char **argv) ++{ ++ PyCompactUnicodeObject o; ++ unsigned char *p = (unsigned char *)(&o); ++ ++ memset(&o, '\0', sizeof(o)); ++ o._base.state.ascii = 1; ++ while (*p == '\0') ++ p++; ++ ++ printf("/* File auto-generated. DO NOT MODIFY MANUALLY */\n"); ++ printf("\n"); ++ printf("#ifndef PYDTRACE_OFFSETS_H\n"); ++ printf("#define PYDTRACE_OFFSETS_H\n"); ++ printf("\n"); ++ printf("#define PYDTRACE_ASCII_OFFSET %ld\n", ++ p - (unsigned char *)(&o)); ++ printf("#define PYDTRACE_ASCII_MASK %d\n", *p); ++ printf("#define PYDTRACE_PyASCIIObject_SIZE %ld\n", ++ sizeof(PyASCIIObject)); ++ printf("#define PYDTRACE_UTF8_LENGTH_OFFSET %ld\n", ++ offsetof(PyCompactUnicodeObject, utf8_length)); ++ printf("#define PYDTRACE_UTF8_OFFSET %ld\n", ++ offsetof(PyCompactUnicodeObject, utf8)); ++ printf("\n"); ++ printf("#endif\n"); ++} ++ +diff --git a/Include/pydtrace_symbols.sh b/Include/pydtrace_symbols.sh +new file mode 100755 +index 0000000000..5672c2bb18 +--- /dev/null ++++ b/Include/pydtrace_symbols.sh +@@ -0,0 +1,15 @@ ++#!/bin/ksh ++ ++obj=${1:?obj} ++ ++# Find the function directly after the one that we want to annotate with ++# the dtrace ustack helper ++ ++func=_PyEval_EvalFrameDefaultReal ++sym=`/usr/bin/nm -hgp $obj \ ++ | grep ' T ' \ ++ | sort -n \ ++ | sed -n "/$func\$/{n;s/.* //;p;}"` ++ ++echo "#define PYDTRACE_AFTER_$func $sym" ++ +diff --git a/Lib/test/dtracedata/jstack.d b/Lib/test/dtracedata/jstack.d +new file mode 100644 +index 0000000000..46855b407c +--- /dev/null ++++ b/Lib/test/dtracedata/jstack.d +@@ -0,0 +1,18 @@ ++ ++python$target:::function-entry ++/copyinstr(arg1)=="test_stack"/ ++{ ++ self->trace = 1; ++} ++python$target:::function-entry ++/self->trace/ ++{ ++ printf("[x]"); ++ jstack(); ++} ++python$target:::function-return ++/copyinstr(arg1)=="test_stack"/ ++{ ++ self->trace = 0; ++} ++ +diff --git a/Lib/test/dtracedata/jstack.d.expected b/Lib/test/dtracedata/jstack.d.expected +new file mode 100644 +index 0000000000..9f1f389df7 +--- /dev/null ++++ b/Lib/test/dtracedata/jstack.d.expected +@@ -0,0 +1,21 @@ ++[x] ++[PyFile:17(test_stack)] ++[x] ++[PyFile:2(function_1)] ++[PyFile:17(test_stack)] ++[x] ++[PyFile:5(function_2)] ++[PyFile:17(test_stack)] ++[x] ++[PyFile:2(function_1)] ++[PyFile:5(function_2)] ++[PyFile:17(test_stack)] ++[x] ++[PyFile:8(function_3)] ++[PyFile:18(test_stack)] ++[x] ++[PyFile:11(function_4)] ++[PyFile:19(test_stack)] ++[x] ++[PyFile:14(function_5)] ++[PyFile:20(test_stack)] +\ No newline at end of file +diff --git a/Lib/test/dtracedata/jstack.py b/Lib/test/dtracedata/jstack.py +new file mode 100644 +index 0000000000..a1584ddf6f +--- /dev/null ++++ b/Lib/test/dtracedata/jstack.py +@@ -0,0 +1,25 @@ ++ ++def function_1(): ++ pass ++ ++def function_2(): ++ function_1() ++ ++def function_3(dummy, dummy2): ++ pass ++ ++def function_4(**dummy): ++ pass ++ ++def function_5(dummy, dummy2, **dummy3): ++ pass ++ ++def test_stack(): ++ function_1() ++ function_2() ++ function_3(*(1,2)) ++ function_4(**{"test":42}) ++ function_5(*(1,2), **{"test":42}) ++ ++test_stack() ++ +diff --git a/Lib/test/dtracedata/unicode-jstack.d b/Lib/test/dtracedata/unicode-jstack.d +new file mode 100644 +index 0000000000..c50a8d6785 +--- /dev/null ++++ b/Lib/test/dtracedata/unicode-jstack.d +@@ -0,0 +1,18 @@ ++ ++python$target:::function-entry ++/copyinstr(arg1)=="test_unicode_stack"/ ++{ ++ self->trace = 1; ++} ++python$target:::function-entry ++/self->trace/ ++{ ++ printf("[x]"); ++ jstack(); ++} ++python$target:::function-return ++/copyinstr(arg1)=="test_unicode_stack"/ ++{ ++ self->trace = 0; ++} ++ +diff --git a/Lib/test/dtracedata/unicode-jstack.d.expected b/Lib/test/dtracedata/unicode-jstack.d.expected +new file mode 100644 +index 0000000000..9e71506738 +--- /dev/null ++++ b/Lib/test/dtracedata/unicode-jstack.d.expected +@@ -0,0 +1,18 @@ ++[x] ++[PyFile:8(test_unicode_stack)] ++[x] ++[PyFile:2(function_1)] ++[PyFile:8(test_unicode_stack)] ++[x] ++[PyFile:9(únícódé)] ++[PyFile:9(test_unicode_stack)] ++[x] ++[PyFile:5(function_2)] ++[PyFile:9(únícódé)] ++[PyFile:9(test_unicode_stack)] ++[x] ++[PyFile:2(function_1)] ++[PyFile:5(function_2)] ++[PyFile:9(únícódé)] ++[PyFile:9(test_unicode_stack)] ++ +diff --git a/Lib/test/dtracedata/unicode-jstack.py b/Lib/test/dtracedata/unicode-jstack.py +new file mode 100644 +index 0000000000..d93e3d76b9 +--- /dev/null ++++ b/Lib/test/dtracedata/unicode-jstack.py +@@ -0,0 +1,15 @@ ++ ++def function_1(): ++ pass ++ ++def function_2(): ++ function_1() ++ ++def test_unicode_stack(): ++ def únícódé(): ++ function_2() ++ function_1() ++ únícódé() ++ ++test_unicode_stack() ++ +diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py +index 6f4c2313dd..fb8ba69209 100644 +--- a/Lib/test/test_dtrace.py ++++ b/Lib/test/test_dtrace.py +@@ -154,6 +154,37 @@ def test_gc(self): + def test_line(self): + self.run_case("line") + ++ def _jstack(self, name): ++ def _jstack_decode(str): ++ # When compiling with '--with-pydebug' ++ str = "".join(re.split(r'\[[0-9]+ refs\]', str)) ++ ++ str = [i for i in str.split("\n") \ ++ if (("[" in i) and not i.endswith(" () ]"))] ++ str = "\n".join(str) ++ str = str.replace("\r", "").replace(" ", "") ++ return str ++ ++ df = abspath(name + self.backend.EXTENSION) ++ pyf = abspath(name + ".py") ++ ++ output = self.backend.trace_python(script_file=df, python_file=pyf, ++ optimize_python=self.optimize_python) ++ ++ actual_result = _jstack_decode(output).replace(pyf, 'PyFile') ++ ++ with open(abspath(name + self.backend.EXTENSION + ".expected")) as f: ++ expected_result = f.read().rstrip() ++ ++ expected_result = expected_result.replace("\r", "").replace(" ", "") ++ ++ self.assertEqual(actual_result, expected_result) ++ ++ def test_jstack(self): ++ self._jstack("jstack") ++ ++ def test_unicode_jstack(self): ++ self._jstack("unicode-jstack") + + class DTraceNormalTests(TraceTests, unittest.TestCase): + backend = DTraceBackend() +diff --git a/Makefile.pre.in b/Makefile.pre.in +index dd5e69f7ab..81e738cf9d 100644 +--- a/Makefile.pre.in ++++ b/Makefile.pre.in +@@ -1572,17 +1572,46 @@ Python/frozen.o: $(FROZEN_FILES_OUT) + # an include guard, so we can't use a pipeline to transform its output. + Include/pydtrace_probes.h: $(srcdir)/Include/pydtrace.d + $(MKDIR_P) Include +- $(DTRACE) $(DFLAGS) -o $@ -h -s $< ++ $(DTRACE) $(DFLAGS) -C -o $@ -h -s $< + : sed in-place edit with POSIX-only tools + sed 's/PYTHON_/PyDTrace_/' $@ > $@.tmp + mv $@.tmp $@ + ++Include/pydtrace_offsets: $(srcdir)/Include/pydtrace_offsets.c ++ $(MKDIR_P) Include ++ $(CC) $(PY_CORE_CFLAGS) -o $@ $< ++ ++Include/pydtrace_offsets.h: Include/pydtrace_offsets ++ $< > $@ ++ ++Include/pydtrace_symbols: $(srcdir)/Include/pydtrace_symbols.sh ++ $(MKDIR_P) Include ++ cp $< $@ ++ chmod +x $@ ++ ++Include/pydtrace_symbols.h: Include/pydtrace_symbols Python/ceval.o ++ $^ > $@ ++ ++Include/dtrace.cpp: $(srcdir)/Include/dtrace.cpp.sh ++ $(MKDIR_P) Include ++ cp $< $@ ++ chmod +x $@ ++ ++clean-dtrace: ++ rm -f Include/dtrace.cpp Include/pydtrace_symbols ++ rm -f Include/pydtrace_offsets Include/pydtrace_offsets.h ++ rm -f Include/pydtrace_symbols.h ++ + Python/ceval.o: $(srcdir)/Include/pydtrace.h + Python/import.o: $(srcdir)/Include/pydtrace.h + Modules/gcmodule.o: $(srcdir)/Include/pydtrace.h + ++Python/pydtrace.o: Include/dtrace.cpp ++Python/pydtrace.o: Include/pydtrace_offsets.h Include/pydtrace_symbols.h + Python/pydtrace.o: $(srcdir)/Include/pydtrace.d $(DTRACE_DEPS) +- $(DTRACE) $(DFLAGS) -o $@ -G -s $< $(DTRACE_DEPS) ++ $(DTRACE) $(DFLAGS) -DPYDTRACE_STACK_HELPER $(PY_CPPFLAGS) \ ++ -C -xcpppath=./Include/dtrace.cpp \ ++ -o $@ -G -s $< $(DTRACE_DEPS) + + Objects/typeobject.o: Objects/typeslots.inc + +@@ -2681,7 +2710,7 @@ profile-removal: + rm -f profile-bolt-stamp + + .PHONY: clean +-clean: clean-retain-profile clean-bolt ++clean: clean-retain-profile clean-bolt clean-dtrace + @if test @DEF_MAKE_ALL_RULE@ = profile-opt -o @DEF_MAKE_ALL_RULE@ = bolt-opt; then \ + rm -f profile-gen-stamp profile-clean-stamp; \ + $(MAKE) profile-removal; \ +diff --git a/Python/ceval.c b/Python/ceval.c +index b44ad31929..5fe0193f62 100644 +--- a/Python/ceval.c ++++ b/Python/ceval.c +@@ -657,7 +657,13 @@ static inline void _Py_LeaveRecursiveCallPy(PyThreadState *tstate) { + #define PY_EVAL_C_STACK_UNITS 2 + + PyObject* _Py_HOT_FUNCTION ++#ifdef WITH_DTRACE ++_PyEval_EvalFrameDefaultReal( ++ long a1, long a2, long a3, long a4, PyThreadState *tstate, int throwflag, ++ _PyInterpreterFrame *frame) ++#else + _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int throwflag) ++#endif + { + _Py_EnsureTstateNotNULL(tstate); + CALL_STAT_INC(pyeval_calls); +@@ -1028,6 +1034,29 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int + # pragma warning(pop) + #endif + ++#ifdef WITH_DTRACE ++ ++/* ++ * These shenanigans look like utter madness, but what we're actually doing is ++ * making sure that the ustack helper will see the PyFrameObject pointer on the ++ * stack. ++ * ++ * We use up the six registers for passing arguments, meaning the call can't ++ * use a register for passing 'f', and has to push it onto the stack in a known ++ * location. ++ */ ++ ++PyObject* __attribute__((noinline)) ++_PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *f, ++ int throwflag) ++{ ++ volatile PyObject *f2; ++ f2 = _PyEval_EvalFrameDefaultReal(0, 0, 0, 0, tstate, throwflag, f); ++ return (PyObject *)f2; ++} ++#endif ++ ++ + static void + format_missing(PyThreadState *tstate, const char *kind, + PyCodeObject *co, PyObject *names, PyObject *qualname) +diff --git a/Python/import.c b/Python/import.c +index db70909982..c3faef09f5 100644 +--- a/Python/import.c ++++ b/Python/import.c +@@ -2774,13 +2774,13 @@ import_find_and_load(PyThreadState *tstate, PyObject *abs_name) + } + + if (PyDTrace_IMPORT_FIND_LOAD_START_ENABLED()) +- PyDTrace_IMPORT_FIND_LOAD_START(PyUnicode_AsUTF8(abs_name)); ++ PyDTrace_IMPORT_FIND_LOAD_START((char *)PyUnicode_AsUTF8(abs_name)); + + mod = PyObject_CallMethodObjArgs(IMPORTLIB(interp), &_Py_ID(_find_and_load), + abs_name, IMPORT_FUNC(interp), NULL); + + if (PyDTrace_IMPORT_FIND_LOAD_DONE_ENABLED()) +- PyDTrace_IMPORT_FIND_LOAD_DONE(PyUnicode_AsUTF8(abs_name), ++ PyDTrace_IMPORT_FIND_LOAD_DONE((char *)PyUnicode_AsUTF8(abs_name), + mod != NULL); + + if (import_time) { +diff --git a/Python/sysmodule.c b/Python/sysmodule.c +index 7874920a16..d0ff239743 100644 +--- a/Python/sysmodule.c ++++ b/Python/sysmodule.c +@@ -237,7 +237,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event, + + /* Dtrace USDT point */ + if (dtrace) { +- PyDTrace_AUDIT(event, (void *)eventArgs); ++ PyDTrace_AUDIT((char *)event, (void *)eventArgs); + } + + /* Call interpreter hooks */ +-- +2.42.0 + diff --git a/build/python313/patches/vendor-packages.patch b/build/python313/patches/vendor-packages.patch new file mode 100644 index 0000000000..7e53bc4b45 --- /dev/null +++ b/build/python313/patches/vendor-packages.patch @@ -0,0 +1,8 @@ +This patch makes Python support the vendor-packages directory. +As it is OmniOS-specific, it is not suitable for upstream. + +diff -wpruN --no-dereference '--exclude=*.orig' a~/Lib/site-packages/vendor-packages.pth a/Lib/site-packages/vendor-packages.pth +--- a~/Lib/site-packages/vendor-packages.pth 1970-01-01 00:00:00 ++++ a/Lib/site-packages/vendor-packages.pth 1970-01-01 00:00:00 +@@ -0,0 +1 @@ ++import site; site.addsitedir('/usr/lib/python3.12/vendor-packages') diff --git a/build/python313/testsuite-d.log b/build/python313/testsuite-d.log new file mode 100644 index 0000000000..9ece5cf570 --- /dev/null +++ b/build/python313/testsuite-d.log @@ -0,0 +1,7 @@ +== Tests result: SUCCESS == + +1 test OK. + +Total tests: run=8 skipped=5 +Total test files: run=1/1 +Result: SUCCESS diff --git a/build/python313/testsuite.log b/build/python313/testsuite.log new file mode 100644 index 0000000000..96a10893fd --- /dev/null +++ b/build/python313/testsuite.log @@ -0,0 +1,22 @@ +== Tests result: SUCCESS == + +27 tests skipped: + test.test_asyncio.test_windows_events + test.test_asyncio.test_windows_utils test.test_gdb.test_backtrace + test.test_gdb.test_cfunction test.test_gdb.test_cfunction_full + test.test_gdb.test_misc test.test_gdb.test_pretty_print + test_android test_dbm_gnu test_epoll test_free_threading test_idle + test_kqueue test_launcher test_msvcrt test_perf_profiler + test_perfmaps test_startfile test_tcl test_tkinter test_ttk + test_ttk_textonly test_turtle test_winapi test_winconsoleio + test_winreg test_wmi + +6 tests skipped (resource denied): + test_smtpnet test_socketserver test_urllib2net test_urllibnet + test_winsound test_zipfile64 + +445 tests OK. + +Total tests: run=44,074 (filtered) skipped=1,955 +Total test files: run=472/478 (filtered) skipped=27 resource_denied=6 +Result: SUCCESS diff --git a/doc/baseline b/doc/baseline index ff71dddcb3..2a88a47180 100644 --- a/doc/baseline +++ b/doc/baseline @@ -512,6 +512,7 @@ omnios runtime/perl/module/sun-solaris omnios runtime/python-27 l omnios runtime/python-311 r omnios runtime/python-312 +omnios runtime/python-313 omnios security/bart omnios security/sudo omnios service/fault-management diff --git a/doc/packages.md b/doc/packages.md index b48502ba1d..95933832ff 100644 --- a/doc/packages.md +++ b/doc/packages.md @@ -82,6 +82,7 @@ | runtime/perl | 5.40.0 | https://www.cpan.org/src/README.html | runtime/python-311 | 3.11.11 | https://www.python.org/downloads/source/ | runtime/python-312 | 3.12.8 | https://www.python.org/downloads/source/ +| runtime/python-313 | 3.13.1 | https://www.python.org/downloads/source/ | security/sudo | 1.9.16p2 | https://www.sudo.ws/ | service/network/chrony | 4.5 | https://download.tuxfamily.org/chrony/ | service/network/ntpsec | 1.2.3 | https://github.com/ntpsec/ntpsec/tags https://blog.ntpsec.org/