| /* POSIX module implementation */ |
| |
| /* This file is also used for Windows NT/MS-Win. In that case the |
| module actually calls itself 'nt', not 'posix', and a few |
| functions are either unimplemented or implemented differently. The source |
| assumes that for Windows NT, the macro 'MS_WINDOWS' is defined independent |
| of the compiler used. Different compilers define their own feature |
| test macro, e.g. '_MSC_VER'. */ |
| |
| #define PY_SSIZE_T_CLEAN |
| |
| #include "Python.h" |
| |
| #ifdef __VXWORKS__ |
| # include "pycore_bitutils.h" // _Py_popcount32() |
| #endif |
| #include "pycore_call.h" // _PyObject_CallNoArgs() |
| #include "pycore_ceval.h" // _PyEval_ReInitThreads() |
| #include "pycore_fileutils.h" // _Py_closerange() |
| #include "pycore_import.h" // _PyImport_ReInitLock() |
| #include "pycore_initconfig.h" // _PyStatus_EXCEPTION() |
| #include "pycore_moduleobject.h" // _PyModule_GetState() |
| #include "pycore_object.h" // _PyObject_LookupSpecial() |
| #include "pycore_pystate.h" // _PyInterpreterState_GET() |
| #include "pycore_signal.h" // Py_NSIG |
| |
| #ifdef MS_WINDOWS |
| # include <windows.h> |
| # if !defined(MS_WINDOWS_GAMES) || defined(MS_WINDOWS_DESKTOP) |
| # include <pathcch.h> |
| # endif |
| # include <winioctl.h> |
| # include <lmcons.h> // UNLEN |
| # include "osdefs.h" // SEP |
| # if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM) |
| # define HAVE_SYMLINK |
| # endif /* MS_WINDOWS_DESKTOP | MS_WINDOWS_SYSTEM */ |
| #endif |
| |
| #include "structmember.h" // PyMemberDef |
| #ifndef MS_WINDOWS |
| # include "posixmodule.h" |
| #else |
| # include "pycore_fileutils_windows.h" |
| # include "winreparse.h" |
| #endif |
| |
| #if !defined(EX_OK) && defined(EXIT_SUCCESS) |
| # define EX_OK EXIT_SUCCESS |
| #endif |
| |
| /* On android API level 21, 'AT_EACCESS' is not declared although |
| * HAVE_FACCESSAT is defined. */ |
| #ifdef __ANDROID__ |
| # undef HAVE_FACCESSAT |
| #endif |
| |
| #include <stdio.h> // ctermid() |
| #include <stdlib.h> // system() |
| |
| /* |
| * A number of APIs are available on macOS from a certain macOS version. |
| * To support building with a new SDK while deploying to older versions |
| * the availability test is split into two: |
| * - HAVE_<FUNCTION>: The configure check for compile time availability |
| * - HAVE_<FUNCTION>_RUNTIME: Runtime check for availability |
| * |
| * The latter is always true when not on macOS, or when using a compiler |
| * that does not support __has_builtin (older versions of Xcode). |
| * |
| * Due to compiler restrictions there is one valid use of HAVE_<FUNCTION>_RUNTIME: |
| * if (HAVE_<FUNCTION>_RUNTIME) { ... } |
| * |
| * In mixing the test with other tests or using negations will result in compile |
| * errors. |
| */ |
| #if defined(__APPLE__) |
| |
| #include <mach/mach.h> |
| |
| #if defined(__has_builtin) |
| #if __has_builtin(__builtin_available) |
| #define HAVE_BUILTIN_AVAILABLE 1 |
| #endif |
| #endif |
| |
| #ifdef HAVE_BUILTIN_AVAILABLE |
| # define HAVE_FSTATAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_FACCESSAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_FCHMODAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_FCHOWNAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_LINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_FDOPENDIR_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_MKDIRAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_RENAMEAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_UNLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_OPENAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_READLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_SYMLINKAT_RUNTIME __builtin_available(macOS 10.10, iOS 8.0, *) |
| # define HAVE_FUTIMENS_RUNTIME __builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) |
| # define HAVE_UTIMENSAT_RUNTIME __builtin_available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) |
| # define HAVE_PWRITEV_RUNTIME __builtin_available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) |
| # define HAVE_MKFIFOAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) |
| # define HAVE_MKNODAT_RUNTIME __builtin_available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) |
| |
| # define HAVE_POSIX_SPAWN_SETSID_RUNTIME __builtin_available(macOS 10.15, *) |
| |
| #else /* Xcode 8 or earlier */ |
| |
| /* __builtin_available is not present in these compilers, but |
| * some of the symbols might be weak linked (10.10 SDK or later |
| * deploying on 10.9. |
| * |
| * Fall back to the older style of availability checking for |
| * symbols introduced in macOS 10.10. |
| */ |
| |
| # ifdef HAVE_FSTATAT |
| # define HAVE_FSTATAT_RUNTIME (fstatat != NULL) |
| # endif |
| |
| # ifdef HAVE_FACCESSAT |
| # define HAVE_FACCESSAT_RUNTIME (faccessat != NULL) |
| # endif |
| |
| # ifdef HAVE_FCHMODAT |
| # define HAVE_FCHMODAT_RUNTIME (fchmodat != NULL) |
| # endif |
| |
| # ifdef HAVE_FCHOWNAT |
| # define HAVE_FCHOWNAT_RUNTIME (fchownat != NULL) |
| # endif |
| |
| # ifdef HAVE_LINKAT |
| # define HAVE_LINKAT_RUNTIME (linkat != NULL) |
| # endif |
| |
| # ifdef HAVE_FDOPENDIR |
| # define HAVE_FDOPENDIR_RUNTIME (fdopendir != NULL) |
| # endif |
| |
| # ifdef HAVE_MKDIRAT |
| # define HAVE_MKDIRAT_RUNTIME (mkdirat != NULL) |
| # endif |
| |
| # ifdef HAVE_RENAMEAT |
| # define HAVE_RENAMEAT_RUNTIME (renameat != NULL) |
| # endif |
| |
| # ifdef HAVE_UNLINKAT |
| # define HAVE_UNLINKAT_RUNTIME (unlinkat != NULL) |
| # endif |
| |
| # ifdef HAVE_OPENAT |
| # define HAVE_OPENAT_RUNTIME (openat != NULL) |
| # endif |
| |
| # ifdef HAVE_READLINKAT |
| # define HAVE_READLINKAT_RUNTIME (readlinkat != NULL) |
| # endif |
| |
| # ifdef HAVE_SYMLINKAT |
| # define HAVE_SYMLINKAT_RUNTIME (symlinkat != NULL) |
| # endif |
| |
| # ifdef HAVE_UTIMENSAT |
| # define HAVE_UTIMENSAT_RUNTIME (utimensat != NULL) |
| # endif |
| |
| # ifdef HAVE_FUTIMENS |
| # define HAVE_FUTIMENS_RUNTIME (futimens != NULL) |
| # endif |
| |
| # ifdef HAVE_PWRITEV |
| # define HAVE_PWRITEV_RUNTIME (pwritev != NULL) |
| # endif |
| |
| # ifdef HAVE_MKFIFOAT |
| # define HAVE_MKFIFOAT_RUNTIME (mkfifoat != NULL) |
| # endif |
| |
| # ifdef HAVE_MKNODAT |
| # define HAVE_MKNODAT_RUNTIME (mknodat != NULL) |
| # endif |
| |
| #endif |
| |
| #ifdef HAVE_FUTIMESAT |
| /* Some of the logic for weak linking depends on this assertion */ |
| # error "HAVE_FUTIMESAT unexpectedly defined" |
| #endif |
| |
| #else |
| # define HAVE_FSTATAT_RUNTIME 1 |
| # define HAVE_FACCESSAT_RUNTIME 1 |
| # define HAVE_FCHMODAT_RUNTIME 1 |
| # define HAVE_FCHOWNAT_RUNTIME 1 |
| # define HAVE_LINKAT_RUNTIME 1 |
| # define HAVE_FDOPENDIR_RUNTIME 1 |
| # define HAVE_MKDIRAT_RUNTIME 1 |
| # define HAVE_RENAMEAT_RUNTIME 1 |
| # define HAVE_UNLINKAT_RUNTIME 1 |
| # define HAVE_OPENAT_RUNTIME 1 |
| # define HAVE_READLINKAT_RUNTIME 1 |
| # define HAVE_SYMLINKAT_RUNTIME 1 |
| # define HAVE_FUTIMENS_RUNTIME 1 |
| # define HAVE_UTIMENSAT_RUNTIME 1 |
| # define HAVE_PWRITEV_RUNTIME 1 |
| # define HAVE_MKFIFOAT_RUNTIME 1 |
| # define HAVE_MKNODAT_RUNTIME 1 |
| #endif |
| |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| PyDoc_STRVAR(posix__doc__, |
| "This module provides access to operating system functionality that is\n\ |
| standardized by the C Standard and the POSIX standard (a thinly\n\ |
| disguised Unix interface). Refer to the library manual and\n\ |
| corresponding Unix manual entries for more information on calls."); |
| |
| |
| #ifdef HAVE_SYS_UIO_H |
| # include <sys/uio.h> |
| #endif |
| |
| #ifdef HAVE_SYS_SYSMACROS_H |
| /* GNU C Library: major(), minor(), makedev() */ |
| # include <sys/sysmacros.h> |
| #endif |
| |
| #ifdef HAVE_SYS_TYPES_H |
| # include <sys/types.h> |
| #endif /* HAVE_SYS_TYPES_H */ |
| |
| #ifdef HAVE_SYS_STAT_H |
| # include <sys/stat.h> |
| #endif /* HAVE_SYS_STAT_H */ |
| |
| #ifdef HAVE_SYS_WAIT_H |
| # include <sys/wait.h> // WNOHANG |
| #endif |
| #ifdef HAVE_LINUX_WAIT_H |
| # include <linux/wait.h> // P_PIDFD |
| #endif |
| |
| #ifdef HAVE_SIGNAL_H |
| # include <signal.h> |
| #endif |
| |
| #ifdef HAVE_FCNTL_H |
| # include <fcntl.h> |
| #endif |
| |
| #ifdef HAVE_GRP_H |
| # include <grp.h> |
| #endif |
| |
| #ifdef HAVE_SYSEXITS_H |
| # include <sysexits.h> |
| #endif |
| |
| #ifdef HAVE_SYS_LOADAVG_H |
| # include <sys/loadavg.h> |
| #endif |
| |
| #ifdef HAVE_SYS_SENDFILE_H |
| # include <sys/sendfile.h> |
| #endif |
| |
| #if defined(__APPLE__) |
| # include <copyfile.h> |
| #endif |
| |
| #ifdef HAVE_SCHED_H |
| # include <sched.h> |
| #endif |
| |
| #ifdef HAVE_COPY_FILE_RANGE |
| # include <unistd.h> |
| #endif |
| |
| #if !defined(CPU_ALLOC) && defined(HAVE_SCHED_SETAFFINITY) |
| # undef HAVE_SCHED_SETAFFINITY |
| #endif |
| |
| #if defined(HAVE_SYS_XATTR_H) && defined(__linux__) && !defined(__FreeBSD_kernel__) && !defined(__GNU__) |
| # define USE_XATTRS |
| # include <linux/limits.h> // Needed for XATTR_SIZE_MAX on musl libc. |
| #endif |
| |
| #ifdef USE_XATTRS |
| # include <sys/xattr.h> |
| #endif |
| |
| #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__APPLE__) |
| # ifdef HAVE_SYS_SOCKET_H |
| # include <sys/socket.h> |
| # endif |
| #endif |
| |
| #ifdef HAVE_DLFCN_H |
| # include <dlfcn.h> |
| #endif |
| |
| #ifdef __hpux |
| # include <sys/mpctl.h> |
| #endif |
| |
| #if defined(__DragonFly__) || \ |
| defined(__OpenBSD__) || \ |
| defined(__FreeBSD__) || \ |
| defined(__NetBSD__) || \ |
| defined(__APPLE__) |
| # include <sys/sysctl.h> |
| #endif |
| |
| #ifdef HAVE_LINUX_RANDOM_H |
| # include <linux/random.h> |
| #endif |
| #ifdef HAVE_GETRANDOM_SYSCALL |
| # include <sys/syscall.h> |
| #endif |
| |
| #ifdef HAVE_WINDOWS_CONSOLE_IO |
| # define TERMSIZE_USE_CONIO |
| #elif defined(HAVE_SYS_IOCTL_H) |
| # include <sys/ioctl.h> |
| # if defined(HAVE_TERMIOS_H) |
| # include <termios.h> |
| # endif |
| # if defined(TIOCGWINSZ) |
| # define TERMSIZE_USE_IOCTL |
| # endif |
| #endif /* HAVE_WINDOWS_CONSOLE_IO */ |
| |
| /* Various compilers have only certain posix functions */ |
| /* XXX Gosh I wish these were all moved into pyconfig.h */ |
| #if defined(__WATCOMC__) && !defined(__QNX__) /* Watcom compiler */ |
| # define HAVE_OPENDIR 1 |
| # define HAVE_SYSTEM 1 |
| # include <process.h> |
| #elif defined( _MSC_VER) |
| /* Microsoft compiler */ |
| # if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_APP) || defined(MS_WINDOWS_SYSTEM) |
| # define HAVE_GETPPID 1 |
| # endif /* MS_WINDOWS_DESKTOP | MS_WINDOWS_APP | MS_WINDOWS_SYSTEM */ |
| # if defined(MS_WINDOWS_DESKTOP) |
| # define HAVE_GETLOGIN 1 |
| # endif /* MS_WINDOWS_DESKTOP */ |
| # if defined(MS_WINDOWS_DESKTOP) || defined(MS_WINDOWS_SYSTEM) |
| # define HAVE_SPAWNV 1 |
| # define HAVE_EXECV 1 |
| # define HAVE_WSPAWNV 1 |
| # define HAVE_WEXECV 1 |
| # define HAVE_SYSTEM 1 |
| # define HAVE_CWAIT 1 |
| # endif /* MS_WINDOWS_DESKTOP | MS_WINDOWS_SYSTEM */ |
| # define HAVE_PIPE 1 |
| # define HAVE_FSYNC 1 |
| # define fsync _commit |
| #endif /* ! __WATCOMC__ || __QNX__ */ |
| |
| /*[clinic input] |
| # one of the few times we lie about this name! |
| module os |
| [clinic start generated code]*/ |
| /*[clinic end generated code: output=da39a3ee5e6b4b0d input=94a0f0f978acae17]*/ |
| |
| #ifndef _MSC_VER |
| |
| #if defined(__sgi)&&_COMPILER_VERSION>=700 |
| /* declare ctermid_r if compiling with MIPSPro 7.x in ANSI C mode |
| (default) */ |
| extern char *ctermid_r(char *); |
| #endif |
| |
| #endif /* !_MSC_VER */ |
| |
| #if defined(__VXWORKS__) |
| # include <vxCpuLib.h> |
| # include <rtpLib.h> |
| # include <wait.h> |
| # include <taskLib.h> |
| # ifndef _P_WAIT |
| # define _P_WAIT 0 |
| # define _P_NOWAIT 1 |
| # define _P_NOWAITO 1 |
| # endif |
| #endif /* __VXWORKS__ */ |
| |
| #ifdef HAVE_POSIX_SPAWN |
| # include <spawn.h> |
| #endif |
| |
| #ifdef HAVE_UTIME_H |
| # include <utime.h> |
| #endif /* HAVE_UTIME_H */ |
| |
| #ifdef HAVE_SYS_UTIME_H |
| # include <sys/utime.h> |
| # define HAVE_UTIME_H /* pretend we do for the rest of this file */ |
| #endif /* HAVE_SYS_UTIME_H */ |
| |
| #ifdef HAVE_SYS_TIMES_H |
| # include <sys/times.h> |
| #endif /* HAVE_SYS_TIMES_H */ |
| |
| #ifdef HAVE_SYS_PARAM_H |
| # include <sys/param.h> |
| #endif /* HAVE_SYS_PARAM_H */ |
| |
| #ifdef HAVE_SYS_UTSNAME_H |
| # include <sys/utsname.h> |
| #endif /* HAVE_SYS_UTSNAME_H */ |
| |
| #ifdef HAVE_DIRENT_H |
| # include <dirent.h> |
| # define NAMLEN(dirent) strlen((dirent)->d_name) |
| #else |
| # if defined(__WATCOMC__) && !defined(__QNX__) |
| # include <direct.h> |
| # define NAMLEN(dirent) strlen((dirent)->d_name) |
| # else |
| # define dirent direct |
| # define NAMLEN(dirent) (dirent)->d_namlen |
| # endif |
| # ifdef HAVE_SYS_NDIR_H |
| # include <sys/ndir.h> |
| # endif |
| # ifdef HAVE_SYS_DIR_H |
| # include <sys/dir.h> |
| # endif |
| # ifdef HAVE_NDIR_H |
| # include <ndir.h> |
| # endif |
| #endif |
| |
| #ifdef _MSC_VER |
| # ifdef HAVE_DIRECT_H |
| # include <direct.h> |
| # endif |
| # ifdef HAVE_IO_H |
| # include <io.h> |
| # endif |
| # ifdef HAVE_PROCESS_H |
| # include <process.h> |
| # endif |
| # include <malloc.h> |
| #endif /* _MSC_VER */ |
| |
| #ifndef MAXPATHLEN |
| # if defined(PATH_MAX) && PATH_MAX > 1024 |
| # define MAXPATHLEN PATH_MAX |
| # else |
| # define MAXPATHLEN 1024 |
| # endif |
| #endif /* MAXPATHLEN */ |
| |
| #ifdef UNION_WAIT |
| /* Emulate some macros on systems that have a union instead of macros */ |
| # ifndef WIFEXITED |
| # define WIFEXITED(u_wait) (!(u_wait).w_termsig && !(u_wait).w_coredump) |
| # endif |
| # ifndef WEXITSTATUS |
| # define WEXITSTATUS(u_wait) (WIFEXITED(u_wait)?((u_wait).w_retcode):-1) |
| # endif |
| # ifndef WTERMSIG |
| # define WTERMSIG(u_wait) ((u_wait).w_termsig) |
| # endif |
| # define WAIT_TYPE union wait |
| # define WAIT_STATUS_INT(s) (s.w_status) |
| #else |
| /* !UNION_WAIT */ |
| # define WAIT_TYPE int |
| # define WAIT_STATUS_INT(s) (s) |
| #endif /* UNION_WAIT */ |
| |
| /* Don't use the "_r" form if we don't need it (also, won't have a |
| prototype for it, at least on Solaris -- maybe others as well?). */ |
| #if defined(HAVE_CTERMID_R) |
| # define USE_CTERMID_R |
| #endif |
| |
| /* choose the appropriate stat and fstat functions and return structs */ |
| #undef STAT |
| #undef FSTAT |
| #undef STRUCT_STAT |
| #ifdef MS_WINDOWS |
| # define STAT win32_stat |
| # define LSTAT win32_lstat |
| # define FSTAT _Py_fstat_noraise |
| # define STRUCT_STAT struct _Py_stat_struct |
| #else |
| # define STAT stat |
| # define LSTAT lstat |
| # define FSTAT fstat |
| # define STRUCT_STAT struct stat |
| #endif |
| |
| #if defined(MAJOR_IN_MKDEV) |
| # include <sys/mkdev.h> |
| #else |
| # if defined(MAJOR_IN_SYSMACROS) |
| # include <sys/sysmacros.h> |
| # endif |
| # if defined(HAVE_MKNOD) && defined(HAVE_SYS_MKDEV_H) |
| # include <sys/mkdev.h> |
| # endif |
| #endif |
| |
| #ifdef MS_WINDOWS |
| # define INITFUNC PyInit_nt |
| # define MODNAME "nt" |
| # define MODNAME_OBJ &_Py_ID(nt) |
| #else |
| # define INITFUNC PyInit_posix |
| # define MODNAME "posix" |
| # define MODNAME_OBJ &_Py_ID(posix) |
| #endif |
| |
| #if defined(__sun) |
| /* Something to implement in autoconf, not present in autoconf 2.69 */ |
| # define HAVE_STRUCT_STAT_ST_FSTYPE 1 |
| #endif |
| |
| /* memfd_create is either defined in sys/mman.h or sys/memfd.h |
| * linux/memfd.h defines additional flags |
| */ |
| #ifdef HAVE_SYS_MMAN_H |
| # include <sys/mman.h> |
| #endif |
| #ifdef HAVE_SYS_MEMFD_H |
| # include <sys/memfd.h> |
| #endif |
| #ifdef HAVE_LINUX_MEMFD_H |
| # include <linux/memfd.h> |
| #endif |
| |
| /* eventfd() */ |
| #ifdef HAVE_SYS_EVENTFD_H |
| # include <sys/eventfd.h> |
| #endif |
| |
| #ifdef _Py_MEMORY_SANITIZER |
| # include <sanitizer/msan_interface.h> |
| #endif |
| |
| #ifdef HAVE_FORK |
| static void |
| run_at_forkers(PyObject *lst, int reverse) |
| { |
| Py_ssize_t i; |
| PyObject *cpy; |
| |
| if (lst != NULL) { |
| assert(PyList_CheckExact(lst)); |
| |
| /* Use a list copy in case register_at_fork() is called from |
| * one of the callbacks. |
| */ |
| cpy = PyList_GetSlice(lst, 0, PyList_GET_SIZE(lst)); |
| if (cpy == NULL) |
| PyErr_WriteUnraisable(lst); |
| else { |
| if (reverse) |
| PyList_Reverse(cpy); |
| for (i = 0; i < PyList_GET_SIZE(cpy); i++) { |
| PyObject *func, *res; |
| func = PyList_GET_ITEM(cpy, i); |
| res = _PyObject_CallNoArgs(func); |
| if (res == NULL) |
| PyErr_WriteUnraisable(func); |
| else |
| Py_DECREF(res); |
| } |
| Py_DECREF(cpy); |
| } |
| } |
| } |
| |
| void |
| PyOS_BeforeFork(void) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| run_at_forkers(interp->before_forkers, 1); |
| |
| _PyImport_AcquireLock(interp); |
| } |
| |
| void |
| PyOS_AfterFork_Parent(void) |
| { |
| PyInterpreterState *interp = _PyInterpreterState_GET(); |
| if (_PyImport_ReleaseLock(interp) <= 0) { |
| Py_FatalError("failed releasing import lock after fork"); |
| } |
| |
| run_at_forkers(interp->after_forkers_parent, 0); |
| } |
| |
| void |
| PyOS_AfterFork_Child(void) |
| { |
| PyStatus status; |
| _PyRuntimeState *runtime = &_PyRuntime; |
| |
| status = _PyRuntimeState_ReInitThreads(runtime); |
| if (_PyStatus_EXCEPTION(status)) { |
| goto fatal_error; |
| } |
| |
| PyThreadState *tstate = _PyThreadState_GET(); |
| _Py_EnsureTstateNotNULL(tstate); |
| |
| #ifdef PY_HAVE_THREAD_NATIVE_ID |
| tstate->native_thread_id = PyThread_get_thread_native_id(); |
| #endif |
| |
| status = _PyEval_ReInitThreads(tstate); |
| if (_PyStatus_EXCEPTION(status)) { |
| goto fatal_error; |
| } |
| |
| status = _PyImport_ReInitLock(tstate->interp); |
| if (_PyStatus_EXCEPTION(status)) { |
| goto fatal_error; |
| } |
| |
| _PySignal_AfterFork(); |
| |
| status = _PyInterpreterState_DeleteExceptMain(runtime); |
| if (_PyStatus_EXCEPTION(status)) { |
| goto fatal_error; |
| } |
| assert(_PyThreadState_GET() == tstate); |
| |
| status = _PyPerfTrampoline_AfterFork_Child(); |
| if (_PyStatus_EXCEPTION(status)) { |
| goto fatal_error; |
| } |
| |
| run_at_forkers(tstate->interp->after_forkers_child, 0); |
| return; |
| |
| fatal_error: |
| Py_ExitStatusException(status); |
| } |
| |
| static int |
| register_at_forker(PyObject **lst, PyObject *func) |
| { |
| if (func == NULL) /* nothing to register? do nothing. */ |
| return 0; |
| if (*lst == NULL) { |
| *lst = PyList_New(0); |
| if (*lst == NULL) |
| return -1; |
| } |
| return PyList_Append(*lst, func); |
| } |
| #endif /* HAVE_FORK */ |
| |
| |
| /* Legacy wrapper */ |
| void |
| PyOS_AfterFork(void) |
| { |
| #ifdef HAVE_FORK |
| PyOS_AfterFork_Child(); |
| #endif |
| } |
| |
| |
| #ifdef MS_WINDOWS |
| /* defined in fileutils.c */ |
| void _Py_time_t_to_FILE_TIME(time_t, int, FILETIME *); |
| void _Py_attribute_data_to_stat(BY_HANDLE_FILE_INFORMATION *, ULONG, |
| FILE_BASIC_INFO *, FILE_ID_INFO *, |
| struct _Py_stat_struct *); |
| void _Py_stat_basic_info_to_stat(FILE_STAT_BASIC_INFORMATION *, |
| struct _Py_stat_struct *); |
| #endif |
| |
| |
| #ifndef MS_WINDOWS |
| PyObject * |
| _PyLong_FromUid(uid_t uid) |
| { |
| if (uid == (uid_t)-1) |
| return PyLong_FromLong(-1); |
| return PyLong_FromUnsignedLong(uid); |
| } |
| |
| PyObject * |
| _PyLong_FromGid(gid_t gid) |
| { |
| if (gid == (gid_t)-1) |
| return PyLong_FromLong(-1); |
| return PyLong_FromUnsignedLong(gid); |
| } |
| |
| int |
| _Py_Uid_Converter(PyObject *obj, uid_t *p) |
| { |
| uid_t uid; |
| PyObject *index; |
| int overflow; |
| long result; |
| unsigned long uresult; |
| |
| index = _PyNumber_Index(obj); |
| if (index == NULL) { |
| PyErr_Format(PyExc_TypeError, |
| "uid should be integer, not %.200s", |
| _PyType_Name(Py_TYPE(obj))); |
| return 0; |
| } |
| |
| /* |
| * Handling uid_t is complicated for two reasons: |
| * * Although uid_t is (always?) unsigned, it still |
| * accepts -1. |
| * * We don't know its size in advance--it may be |
| * bigger than an int, or it may be smaller than |
| * a long. |
| * |
| * So a bit of defensive programming is in order. |
| * Start with interpreting the value passed |
| * in as a signed long and see if it works. |
| */ |
| |
| result = PyLong_AsLongAndOverflow(index, &overflow); |
| |
| if (!overflow) { |
| uid = (uid_t)result; |
| |
| if (result == -1) { |
| if (PyErr_Occurred()) |
| goto fail; |
| /* It's a legitimate -1, we're done. */ |
| goto success; |
| } |
| |
| /* Any other negative number is disallowed. */ |
| if (result < 0) |
| goto underflow; |
| |
| /* Ensure the value wasn't truncated. */ |
| if (sizeof(uid_t) < sizeof(long) && |
| (long)uid != result) |
| goto underflow; |
| goto success; |
| } |
| |
| if (overflow < 0) |
| goto underflow; |
| |
| /* |
| * Okay, the value overflowed a signed long. If it |
| * fits in an *unsigned* long, it may still be okay, |
| * as uid_t may be unsigned long on this platform. |
| */ |
| uresult = PyLong_AsUnsignedLong(index); |
| if (PyErr_Occurred()) { |
| if (PyErr_ExceptionMatches(PyExc_OverflowError)) |
| goto overflow; |
| goto fail; |
| } |
| |
| uid = (uid_t)uresult; |
| |
| /* |
| * If uid == (uid_t)-1, the user actually passed in ULONG_MAX, |
| * but this value would get interpreted as (uid_t)-1 by chown |
| * and its siblings. That's not what the user meant! So we |
| * throw an overflow exception instead. (We already |
| * handled a real -1 with PyLong_AsLongAndOverflow() above.) |
| */ |
| if (uid == (uid_t)-1) |
| goto overflow; |
| |
| /* Ensure the value wasn't truncated. */ |
| if (sizeof(uid_t) < sizeof(long) && |
| (unsigned long)uid != uresult) |
| goto overflow; |
| /* fallthrough */ |
| |
| success: |
| Py_DECREF(index); |
| *p = uid; |
| return 1; |
| |
| underflow: |
| PyErr_SetString(PyExc_OverflowError, |
| "uid is less than minimum"); |
| goto fail; |
| |
| overflow: |
| PyErr_SetString(PyExc_OverflowError, |
| "uid is greater than maximum"); |
| /* fallthrough */ |
| |
| fail: |
| Py_DECREF(index); |
| return 0; |
| } |
| |
| int |
| _Py_Gid_Converter(PyObject *obj, gid_t *p) |
| { |
| gid_t gid; |
| PyObject *index; |
| int overflow; |
| long result; |
| unsigned long uresult; |
| |
| index = _PyNumber_Index(obj); |
| if (index == NULL) { |
| PyErr_Format(PyExc_TypeError, |
| "gid should be integer, not %.200s", |
| _PyType_Name(Py_TYPE(obj))); |
| return 0; |
| } |
| |
| /* |
| * Handling gid_t is complicated for two reasons: |
| * * Although gid_t is (always?) unsigned, it still |
| * accepts -1. |
| * * We don't know its size in advance--it may be |
| * bigger than an int, or it may be smaller than |
| * a long. |
| * |
| * So a bit of defensive programming is in order. |
| * Start with interpreting the value passed |
| * in as a signed long and see if it works. |
| */ |
| |
| result = PyLong_AsLongAndOverflow(index, &overflow); |
| |
| if (!overflow) { |
| gid = (gid_t)result; |
| |
| if (result == -1) { |
| if (PyErr_Occurred()) |
| goto fail; |
| /* It's a legitimate -1, we're done. */ |
| goto success; |
| } |
| |
| /* Any other negative number is disallowed. */ |
| if (result < 0) { |
| goto underflow; |
| } |
| |
| /* Ensure the value wasn't truncated. */ |
| if (sizeof(gid_t) < sizeof(long) && |
| (long)gid != result) |
| goto underflow; |
| goto success; |
| } |
| |
| if (overflow < 0) |
| goto underflow; |
| |
| /* |
| * Okay, the value overflowed a signed long. If it |
| * fits in an *unsigned* long, it may still be okay, |
| * as gid_t may be unsigned long on this platform. |
| */ |
| uresult = PyLong_AsUnsignedLong(index); |
| if (PyErr_Occurred()) { |
| if (PyErr_ExceptionMatches(PyExc_OverflowError)) |
| goto overflow; |
| goto fail; |
| } |
| |
| gid = (gid_t)uresult; |
| |
| /* |
| * If gid == (gid_t)-1, the user actually passed in ULONG_MAX, |
| * but this value would get interpreted as (gid_t)-1 by chown |
| * and its siblings. That's not what the user meant! So we |
| * throw an overflow exception instead. (We already |
| * handled a real -1 with PyLong_AsLongAndOverflow() above.) |
| */ |
| if (gid == (gid_t)-1) |
| goto overflow; |
| |
| /* Ensure the value wasn't truncated. */ |
| if (sizeof(gid_t) < sizeof(long) && |
| (unsigned long)gid != uresult) |
| goto overflow; |
| /* fallthrough */ |
| |
| success: |
| Py_DECREF(index); |
| *p = gid; |
| return 1; |
| |
| underflow: |
| PyErr_SetString(PyExc_OverflowError, |
| "gid is less than minimum"); |
| goto fail; |
| |
| overflow: |
| PyErr_SetString(PyExc_OverflowError, |
| "gid is greater than maximum"); |
| /* fallthrough */ |
| |
| fail: |
| Py_DECREF(index); |
| return 0; |
| } |
| #endif /* MS_WINDOWS */ |
| |
| |
| #define _PyLong_FromDev PyLong_FromLongLong |
| |
| |
| #if (defined(HAVE_MKNOD) && defined(HAVE_MAKEDEV)) || defined(HAVE_DEVICE_MACROS) |
| static int |
| _Py_Dev_Converter(PyObject *obj, void *p) |
| { |
| *((dev_t *)p) = PyLong_AsUnsignedLongLong(obj); |
| if (PyErr_Occurred()) |
| return 0; |
| return 1; |
| } |
| #endif /* (HAVE_MKNOD && HAVE_MAKEDEV) || HAVE_DEVICE_MACROS */ |
| |
| |
| #ifdef AT_FDCWD |
| /* |
| * Why the (int) cast? Solaris 10 defines AT_FDCWD as 0xffd19553 (-3041965); |
| * without the int cast, the value gets interpreted as uint (4291925331), |
| * which doesn't play nicely with all the initializer lines in this file that |
| * look like this: |
| * int dir_fd = DEFAULT_DIR_FD; |
| */ |
| #define DEFAULT_DIR_FD (int)AT_FDCWD |
| #else |
| #define DEFAULT_DIR_FD (-100) |
| #endif |
| |
| static int |
| _fd_converter(PyObject *o, int *p) |
| { |
| int overflow; |
| long long_value; |
| |
| PyObject *index = _PyNumber_Index(o); |
| if (index == NULL) { |
| return 0; |
| } |
| |
| assert(PyLong_Check(index)); |
| long_value = PyLong_AsLongAndOverflow(index, &overflow); |
| Py_DECREF(index); |
| assert(!PyErr_Occurred()); |
| if (overflow > 0 || long_value > INT_MAX) { |
| PyErr_SetString(PyExc_OverflowError, |
| "fd is greater than maximum"); |
| return 0; |
| } |
| if (overflow < 0 || long_value < INT_MIN) { |
| PyErr_SetString(PyExc_OverflowError, |
| "fd is less than minimum"); |
| return 0; |
| } |
| |
| *p = (int)long_value; |
| return 1; |
| } |
| |
| static int |
| dir_fd_converter(PyObject *o, void *p) |
| { |
| if (o == Py_None) { |
| *(int *)p = DEFAULT_DIR_FD; |
| return 1; |
| } |
| else if (PyIndex_Check(o)) { |
| return _fd_converter(o, (int *)p); |
| } |
| else { |
| PyErr_Format(PyExc_TypeError, |
| "argument should be integer or None, not %.200s", |
| _PyType_Name(Py_TYPE(o))); |
| return 0; |
| } |
| } |
| |
| typedef struct { |
| PyObject *billion; |
| PyObject *DirEntryType; |
| PyObject *ScandirIteratorType; |
| #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) |
| PyObject *SchedParamType; |
| #endif |
| newfunc statresult_new_orig; |
| PyObject *StatResultType; |
| PyObject *StatVFSResultType; |
| PyObject *TerminalSizeType; |
| PyObject *TimesResultType; |
| PyObject *UnameResultType; |
| #if defined(HAVE_WAITID) && !defined(__APPLE__) |
| PyObject *WaitidResultType; |
| #endif |
| #if defined(HAVE_WAIT3) || defined(HAVE_WAIT4) |
| PyObject *struct_rusage; |
| #endif |
| PyObject *st_mode; |
| } _posixstate; |
| |
| |
| static inline _posixstate* |
| get_posix_state(PyObject *module) |
| { |
| void *state = _PyModule_GetState(module); |
| assert(state != NULL); |
| return (_posixstate *)state; |
| } |
| |
| /* |
| * A PyArg_ParseTuple "converter" function |
| * that handles filesystem paths in the manner |
| * preferred by the os module. |
| * |
| * path_converter accepts (Unicode) strings and their |
| * subclasses, and bytes and their subclasses. What |
| * it does with the argument depends on the platform: |
| * |
| * * On Windows, if we get a (Unicode) string we |
| * extract the wchar_t * and return it; if we get |
| * bytes we decode to wchar_t * and return that. |
| * |
| * * On all other platforms, strings are encoded |
| * to bytes using PyUnicode_FSConverter, then we |
| * extract the char * from the bytes object and |
| * return that. |
| * |
| * path_converter also optionally accepts signed |
| * integers (representing open file descriptors) instead |
| * of path strings. |
| * |
| * Input fields: |
| * path.nullable |
| * If nonzero, the path is permitted to be None. |
| * path.allow_fd |
| * If nonzero, the path is permitted to be a file handle |
| * (a signed int) instead of a string. |
| * path.function_name |
| * If non-NULL, path_converter will use that as the name |
| * of the function in error messages. |
| * (If path.function_name is NULL it omits the function name.) |
| * path.argument_name |
| * If non-NULL, path_converter will use that as the name |
| * of the parameter in error messages. |
| * (If path.argument_name is NULL it uses "path".) |
| * |
| * Output fields: |
| * path.wide |
| * Points to the path if it was expressed as Unicode |
| * and was not encoded. (Only used on Windows.) |
| * path.narrow |
| * Points to the path if it was expressed as bytes, |
| * or it was Unicode and was encoded to bytes. (On Windows, |
| * is a non-zero integer if the path was expressed as bytes. |
| * The type is deliberately incompatible to prevent misuse.) |
| * path.fd |
| * Contains a file descriptor if path.accept_fd was true |
| * and the caller provided a signed integer instead of any |
| * sort of string. |
| * |
| * WARNING: if your "path" parameter is optional, and is |
| * unspecified, path_converter will never get called. |
| * So if you set allow_fd, you *MUST* initialize path.fd = -1 |
| * yourself! |
| * path.length |
| * The length of the path in characters, if specified as |
| * a string. |
| * path.object |
| * The original object passed in (if get a PathLike object, |
| * the result of PyOS_FSPath() is treated as the original object). |
| * Own a reference to the object. |
| * path.cleanup |
| * For internal use only. May point to a temporary object. |
| * (Pay no attention to the man behind the curtain.) |
| * |
| * At most one of path.wide or path.narrow will be non-NULL. |
| * If path was None and path.nullable was set, |
| * or if path was an integer and path.allow_fd was set, |
| * both path.wide and path.narrow will be NULL |
| * and path.length will be 0. |
| * |
| * path_converter takes care to not write to the path_t |
| * unless it's successful. However it must reset the |
| * "cleanup" field each time it's called. |
| * |
| * Use as follows: |
| * path_t path; |
| * memset(&path, 0, sizeof(path)); |
| * PyArg_ParseTuple(args, "O&", path_converter, &path); |
| * // ... use values from path ... |
| * path_cleanup(&path); |
| * |
| * (Note that if PyArg_Parse fails you don't need to call |
| * path_cleanup(). However it is safe to do so.) |
| */ |
| typedef struct { |
| const char *function_name; |
| const char *argument_name; |
| int nullable; |
| int allow_fd; |
| const wchar_t *wide; |
| #ifdef MS_WINDOWS |
| BOOL narrow; |
| #else |
| const char *narrow; |
| #endif |
| int fd; |
| Py_ssize_t length; |
| PyObject *object; |
| PyObject *cleanup; |
| } path_t; |
| |
| #ifdef MS_WINDOWS |
| #define PATH_T_INITIALIZE(function_name, argument_name, nullable, allow_fd) \ |
| {function_name, argument_name, nullable, allow_fd, NULL, FALSE, -1, 0, NULL, NULL} |
| #else |
| #define PATH_T_INITIALIZE(function_name, argument_name, nullable, allow_fd) \ |
| {function_name, argument_name, nullable, allow_fd, NULL, NULL, -1, 0, NULL, NULL} |
| #endif |
| |
| static void |
| path_cleanup(path_t *path) |
| { |
| wchar_t *wide = (wchar_t *)path->wide; |
| path->wide = NULL; |
| PyMem_Free(wide); |
| Py_CLEAR(path->object); |
| Py_CLEAR(path->cleanup); |
| } |
| |
| static int |
| path_converter(PyObject *o, void *p) |
| { |
| path_t *path = (path_t *)p; |
| PyObject *bytes = NULL; |
| Py_ssize_t length = 0; |
| int is_index, is_bytes, is_unicode; |
| const char *narrow; |
| #ifdef MS_WINDOWS |
| PyObject *wo = NULL; |
| wchar_t *wide = NULL; |
| #endif |
| |
| #define FORMAT_EXCEPTION(exc, fmt) \ |
| PyErr_Format(exc, "%s%s" fmt, \ |
| path->function_name ? path->function_name : "", \ |
| path->function_name ? ": " : "", \ |
| path->argument_name ? path->argument_name : "path") |
| |
| /* Py_CLEANUP_SUPPORTED support */ |
| if (o == NULL) { |
| path_cleanup(path); |
| return 1; |
| } |
| |
| /* Ensure it's always safe to call path_cleanup(). */ |
| path->object = path->cleanup = NULL; |
| /* path->object owns a reference to the original object */ |
| Py_INCREF(o); |
| |
| if ((o == Py_None) && path->nullable) { |
| path->wide = NULL; |
| #ifdef MS_WINDOWS |
| path->narrow = FALSE; |
| #else |
| path->narrow = NULL; |
| #endif |
| path->fd = -1; |
| goto success_exit; |
| } |
| |
| /* Only call this here so that we don't treat the return value of |
| os.fspath() as an fd or buffer. */ |
| is_index = path->allow_fd && PyIndex_Check(o); |
| is_bytes = PyBytes_Check(o); |
| is_unicode = PyUnicode_Check(o); |
| |
| if (!is_index && !is_unicode && !is_bytes) { |
| /* Inline PyOS_FSPath() for better error messages. */ |
| PyObject *func, *res; |
| |
| func = _PyObject_LookupSpecial(o, &_Py_ID(__fspath__)); |
| if (NULL == func) { |
| goto error_format; |
| } |
| res = _PyObject_CallNoArgs(func); |
| Py_DECREF(func); |
| if (NULL == res) { |
| goto error_exit; |
| } |
| else if (PyUnicode_Check(res)) { |
| is_unicode = 1; |
| } |
| else if (PyBytes_Check(res)) { |
| is_bytes = 1; |
| } |
| else { |
| PyErr_Format(PyExc_TypeError, |
| "expected %.200s.__fspath__() to return str or bytes, " |
| "not %.200s", _PyType_Name(Py_TYPE(o)), |
| _PyType_Name(Py_TYPE(res))); |
| Py_DECREF(res); |
| goto error_exit; |
| } |
| |
| /* still owns a reference to the original object */ |
| Py_SETREF(o, res); |
| } |
| |
| if (is_unicode) { |
| #ifdef MS_WINDOWS |
| wide = PyUnicode_AsWideCharString(o, &length); |
| if (!wide) { |
| goto error_exit; |
| } |
| if (length > 32767) { |
| FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows"); |
| goto error_exit; |
| } |
| if (wcslen(wide) != length) { |
| FORMAT_EXCEPTION(PyExc_ValueError, "embedded null character in %s"); |
| goto error_exit; |
| } |
| |
| path->wide = wide; |
| path->narrow = FALSE; |
| path->fd = -1; |
| wide = NULL; |
| goto success_exit; |
| #else |
| if (!PyUnicode_FSConverter(o, &bytes)) { |
| goto error_exit; |
| } |
| #endif |
| } |
| else if (is_bytes) { |
| bytes = Py_NewRef(o); |
| } |
| else if (is_index) { |
| if (!_fd_converter(o, &path->fd)) { |
| goto error_exit; |
| } |
| path->wide = NULL; |
| #ifdef MS_WINDOWS |
| path->narrow = FALSE; |
| #else |
| path->narrow = NULL; |
| #endif |
| goto success_exit; |
| } |
| else { |
| error_format: |
| PyErr_Format(PyExc_TypeError, "%s%s%s should be %s, not %.200s", |
| path->function_name ? path->function_name : "", |
| path->function_name ? ": " : "", |
| path->argument_name ? path->argument_name : "path", |
| path->allow_fd && path->nullable ? "string, bytes, os.PathLike, " |
| "integer or None" : |
| path->allow_fd ? "string, bytes, os.PathLike or integer" : |
| path->nullable ? "string, bytes, os.PathLike or None" : |
| "string, bytes or os.PathLike", |
| _PyType_Name(Py_TYPE(o))); |
| goto error_exit; |
| } |
| |
| length = PyBytes_GET_SIZE(bytes); |
| narrow = PyBytes_AS_STRING(bytes); |
| if ((size_t)length != strlen(narrow)) { |
| FORMAT_EXCEPTION(PyExc_ValueError, "embedded null character in %s"); |
| goto error_exit; |
| } |
| |
| #ifdef MS_WINDOWS |
| wo = PyUnicode_DecodeFSDefaultAndSize( |
| narrow, |
| length |
| ); |
| if (!wo) { |
| goto error_exit; |
| } |
| |
| wide = PyUnicode_AsWideCharString(wo, &length); |
| Py_DECREF(wo); |
| if (!wide) { |
| goto error_exit; |
| } |
| if (length > 32767) { |
| FORMAT_EXCEPTION(PyExc_ValueError, "%s too long for Windows"); |
| goto error_exit; |
| } |
| if (wcslen(wide) != length) { |
| FORMAT_EXCEPTION(PyExc_ValueError, "embedded null character in %s"); |
| goto error_exit; |
| } |
| path->wide = wide; |
| path->narrow = TRUE; |
| Py_DECREF(bytes); |
| wide = NULL; |
| #else |
| path->wide = NULL; |
| path->narrow = narrow; |
| if (bytes == o) { |
| /* Still a reference owned by path->object, don't have to |
| worry about path->narrow is used after free. */ |
| Py_DECREF(bytes); |
| } |
| else { |
| path->cleanup = bytes; |
| } |
| #endif |
| path->fd = -1; |
| |
| success_exit: |
| path->length = length; |
| path->object = o; |
| return Py_CLEANUP_SUPPORTED; |
| |
| error_exit: |
| Py_XDECREF(o); |
| Py_XDECREF(bytes); |
| #ifdef MS_WINDOWS |
| PyMem_Free(wide); |
| #endif |
| return 0; |
| } |
| |
| static void |
| argument_unavailable_error(const char *function_name, const char *argument_name) |
| { |
| PyErr_Format(PyExc_NotImplementedError, |
| "%s%s%s unavailable on this platform", |
| (function_name != NULL) ? function_name : "", |
| (function_name != NULL) ? ": ": "", |
| argument_name); |
| } |
| |
| static int |
| dir_fd_unavailable(PyObject *o, void *p) |
| { |
| int dir_fd; |
| if (!dir_fd_converter(o, &dir_fd)) |
| return 0; |
| if (dir_fd != DEFAULT_DIR_FD) { |
| argument_unavailable_error(NULL, "dir_fd"); |
| return 0; |
| } |
| *(int *)p = dir_fd; |
| return 1; |
| } |
| |
| static int |
| fd_specified(const char *function_name, int fd) |
| { |
| if (fd == -1) |
| return 0; |
| |
| argument_unavailable_error(function_name, "fd"); |
| return 1; |
| } |
| |
| static int |
| follow_symlinks_specified(const char *function_name, int follow_symlinks) |
| { |
| if (follow_symlinks) |
| return 0; |
| |
| argument_unavailable_error(function_name, "follow_symlinks"); |
| return 1; |
| } |
| |
| static int |
| path_and_dir_fd_invalid(const char *function_name, path_t *path, int dir_fd) |
| { |
| if (!path->wide && (dir_fd != DEFAULT_DIR_FD) |
| #ifndef MS_WINDOWS |
| && !path->narrow |
| #endif |
| ) { |
| PyErr_Format(PyExc_ValueError, |
| "%s: can't specify dir_fd without matching path", |
| function_name); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int |
| dir_fd_and_fd_invalid(const char *function_name, int dir_fd, int fd) |
| { |
| if ((dir_fd != DEFAULT_DIR_FD) && (fd != -1)) { |
| PyErr_Format(PyExc_ValueError, |
| "%s: can't specify both dir_fd and fd", |
| function_name); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int |
| fd_and_follow_symlinks_invalid(const char *function_name, int fd, |
| int follow_symlinks) |
| { |
| if ((fd > 0) && (!follow_symlinks)) { |
| PyErr_Format(PyExc_ValueError, |
| "%s: cannot use fd and follow_symlinks together", |
| function_name); |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int |
| dir_fd_and_follow_symlinks_invalid(const char *function_name, int dir_fd, |
| int follow_symlinks) |
| { |
| if ((dir_fd != DEFAULT_DIR_FD) && (!follow_symlinks)) { |
| PyErr_Format(PyExc_ValueError, |
| "%s: cannot use dir_fd and follow_symlinks together", |
| function_name); |
| return 1; |
| } |
| return 0; |
| } |
| |
| #ifdef MS_WINDOWS |
| typedef long long Py_off_t; |
| #else |
| typedef off_t Py_off_t; |
| #endif |
| |
| static int |
| Py_off_t_converter(PyObject *arg, void *addr) |
| { |
| #ifdef HAVE_LARGEFILE_SUPPORT |
| *((Py_off_t *)addr) = PyLong_AsLongLong(arg); |
| #else |
| *((Py_off_t *)addr) = PyLong_AsLong(arg); |
| #endif |
| if (PyErr_Occurred()) |
| return 0; |
| return 1; |
| } |
| |
| static PyObject * |
| PyLong_FromPy_off_t(Py_off_t offset) |
| { |
| #ifdef HAVE_LARGEFILE_SUPPORT |
| return PyLong_FromLongLong(offset); |
| #else |
| return PyLong_FromLong(offset); |
| #endif |
| } |
| |
| #ifdef HAVE_SIGSET_T |
| /* Convert an iterable of integers to a sigset. |
| Return 1 on success, return 0 and raise an exception on error. */ |
| int |
| _Py_Sigset_Converter(PyObject *obj, void *addr) |
| { |
| sigset_t *mask = (sigset_t *)addr; |
| PyObject *iterator, *item; |
| long signum; |
| int overflow; |
| |
| // The extra parens suppress the unreachable-code warning with clang on MacOS |
| if (sigemptyset(mask) < (0)) { |
| /* Probably only if mask == NULL. */ |
| PyErr_SetFromErrno(PyExc_OSError); |
| return 0; |
| } |
| |
| iterator = PyObject_GetIter(obj); |
| if (iterator == NULL) { |
| return 0; |
| } |
| |
| while ((item = PyIter_Next(iterator)) != NULL) { |
| signum = PyLong_AsLongAndOverflow(item, &overflow); |
| Py_DECREF(item); |
| if (signum <= 0 || signum >= Py_NSIG) { |
| if (overflow || signum != -1 || !PyErr_Occurred()) { |
| PyErr_Format(PyExc_ValueError, |
| "signal number %ld out of range [1; %i]", |
| signum, Py_NSIG - 1); |
| } |
| goto error; |
| } |
| if (sigaddset(mask, (int)signum)) { |
| if (errno != EINVAL) { |
| /* Probably impossible */ |
| PyErr_SetFromErrno(PyExc_OSError); |
| goto error; |
| } |
| /* For backwards compatibility, allow idioms such as |
| * `range(1, NSIG)` but warn about invalid signal numbers |
| */ |
| const char msg[] = |
| "invalid signal number %ld, please use valid_signals()"; |
| if (PyErr_WarnFormat(PyExc_RuntimeWarning, 1, msg, signum)) { |
| goto error; |
| } |
| } |
| } |
| if (!PyErr_Occurred()) { |
| Py_DECREF(iterator); |
| return 1; |
| } |
| |
| error: |
| Py_DECREF(iterator); |
| return 0; |
| } |
| #endif /* HAVE_SIGSET_T */ |
| |
| /* Return a dictionary corresponding to the POSIX environment table */ |
| #if defined(WITH_NEXT_FRAMEWORK) || (defined(__APPLE__) && defined(Py_ENABLE_SHARED)) |
| /* On Darwin/MacOSX a shared library or framework has no access to |
| ** environ directly, we must obtain it with _NSGetEnviron(). See also |
| ** man environ(7). |
| */ |
| #include <crt_externs.h> |
| #elif !defined(_MSC_VER) && (!defined(__WATCOMC__) || defined(__QNX__) || defined(__VXWORKS__)) |
| extern char **environ; |
| #endif /* !_MSC_VER */ |
| |
| static PyObject * |
| convertenviron(void) |
| { |
| PyObject *d; |
| #ifdef MS_WINDOWS |
| wchar_t **e; |
| #else |
| char **e; |
| #endif |
| |
| d = PyDict_New(); |
| if (d == NULL) |
| return NULL; |
| #ifdef MS_WINDOWS |
| /* _wenviron must be initialized in this way if the program is started |
| through main() instead of wmain(). */ |
| (void)_wgetenv(L""); |
| e = _wenviron; |
| #elif defined(WITH_NEXT_FRAMEWORK) || (defined(__APPLE__) && defined(Py_ENABLE_SHARED)) |
| /* environ is not accessible as an extern in a shared object on OSX; use |
| _NSGetEnviron to resolve it. The value changes if you add environment |
| variables between calls to Py_Initialize, so don't cache the value. */ |
| e = *_NSGetEnviron(); |
| #else |
| e = environ; |
| #endif |
| if (e == NULL) |
| return d; |
| for (; *e != NULL; e++) { |
| PyObject *k; |
| PyObject *v; |
| #ifdef MS_WINDOWS |
| const wchar_t *p = wcschr(*e, L'='); |
| #else |
| const char *p = strchr(*e, '='); |
| #endif |
| if (p == NULL) |
| continue; |
| #ifdef MS_WINDOWS |
| k = PyUnicode_FromWideChar(*e, (Py_ssize_t)(p-*e)); |
| #else |
| k = PyBytes_FromStringAndSize(*e, (int)(p-*e)); |
| #endif |
| if (k == NULL) { |
| Py_DECREF(d); |
| return NULL; |
| } |
| #ifdef MS_WINDOWS |
| v = PyUnicode_FromWideChar(p+1, wcslen(p+1)); |
| #else |
| v = PyBytes_FromStringAndSize(p+1, strlen(p+1)); |
| #endif |
| if (v == NULL) { |
| Py_DECREF(k); |
| Py_DECREF(d); |
| return NULL; |
| } |
| if (PyDict_SetDefault(d, k, v) == NULL) { |
| Py_DECREF(v); |
| Py_DECREF(k); |
| Py_DECREF(d); |
| return NULL; |
| } |
| Py_DECREF(k); |
| Py_DECREF(v); |
| } |
| return d; |
| } |
| |
| /* Set a POSIX-specific error from errno, and return NULL */ |
| |
| static PyObject * |
| posix_error(void) |
| { |
| return PyErr_SetFromErrno(PyExc_OSError); |
| } |
| |
| #ifdef MS_WINDOWS |
| static PyObject * |
| win32_error(const char* function, const char* filename) |
| { |
| /* XXX We should pass the function name along in the future. |
| (winreg.c also wants to pass the function name.) |
| This would however require an additional param to the |
| Windows error object, which is non-trivial. |
| */ |
| errno = GetLastError(); |
| if (filename) |
| return PyErr_SetFromWindowsErrWithFilename(errno, filename); |
| else |
| return PyErr_SetFromWindowsErr(errno); |
| } |
| |
| static PyObject * |
| win32_error_object_err(const char* function, PyObject* filename, DWORD err) |
| { |
| /* XXX - see win32_error for comments on 'function' */ |
| if (filename) |
| return PyErr_SetExcFromWindowsErrWithFilenameObject( |
| PyExc_OSError, |
| err, |
| filename); |
| else |
| return PyErr_SetFromWindowsErr(err); |
| } |
| |
| static PyObject * |
| win32_error_object(const char* function, PyObject* filename) |
| { |
| errno = GetLastError(); |
| return win32_error_object_err(function, filename, errno); |
| } |
| |
| #endif /* MS_WINDOWS */ |
| |
| static PyObject * |
| posix_path_object_error(PyObject *path) |
| { |
| return PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); |
| } |
| |
| static PyObject * |
| path_object_error(PyObject *path) |
| { |
| #ifdef MS_WINDOWS |
| return PyErr_SetExcFromWindowsErrWithFilenameObject( |
| PyExc_OSError, 0, path); |
| #else |
| return posix_path_object_error(path); |
| #endif |
| } |
| |
| static PyObject * |
| path_object_error2(PyObject *path, PyObject *path2) |
| { |
| #ifdef MS_WINDOWS |
| return PyErr_SetExcFromWindowsErrWithFilenameObjects( |
| PyExc_OSError, 0, path, path2); |
| #else |
| return PyErr_SetFromErrnoWithFilenameObjects(PyExc_OSError, path, path2); |
| #endif |
| } |
| |
| static PyObject * |
| path_error(path_t *path) |
| { |
| return path_object_error(path->object); |
| } |
| |
| static PyObject * |
| posix_path_error(path_t *path) |
| { |
| return posix_path_object_error(path->object); |
| } |
| |
| static PyObject * |
| path_error2(path_t *path, path_t *path2) |
| { |
| return path_object_error2(path->object, path2->object); |
| } |
| |
| |
| /* POSIX generic methods */ |
| |
| static PyObject * |
| posix_fildes_fd(int fd, int (*func)(int)) |
| { |
| int res; |
| int async_err = 0; |
| |
| do { |
| Py_BEGIN_ALLOW_THREADS |
| _Py_BEGIN_SUPPRESS_IPH |
| res = (*func)(fd); |
| _Py_END_SUPPRESS_IPH |
| Py_END_ALLOW_THREADS |
| } while (res != 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); |
| if (res != 0) |
| return (!async_err) ? posix_error() : NULL; |
| Py_RETURN_NONE; |
| } |
| |
| |
| #ifdef MS_WINDOWS |
| /* This is a reimplementation of the C library's chdir function, |
| but one that produces Win32 errors instead of DOS error codes. |
| chdir is essentially a wrapper around SetCurrentDirectory; however, |
| it also needs to set "magic" environment variables indicating |
| the per-drive current directory, which are of the form =<drive>: */ |
| static BOOL __stdcall |
| win32_wchdir(LPCWSTR path) |
| { |
| wchar_t path_buf[MAX_PATH], *new_path = path_buf; |
| int result; |
| wchar_t env[4] = L"=x:"; |
| |
| if(!SetCurrentDirectoryW(path)) |
| return FALSE; |
| result = GetCurrentDirectoryW(Py_ARRAY_LENGTH(path_buf), new_path); |
| if (!result) |
| return FALSE; |
| if (result > Py_ARRAY_LENGTH(path_buf)) { |
| new_path = PyMem_RawMalloc(result * sizeof(wchar_t)); |
| if (!new_path) { |
| SetLastError(ERROR_OUTOFMEMORY); |
| return FALSE; |
| } |
| result = GetCurrentDirectoryW(result, new_path); |
| if (!result) { |
| PyMem_RawFree(new_path); |
| return FALSE; |
| } |
| } |
| int is_unc_like_path = (wcsncmp(new_path, L"\\\\", 2) == 0 || |
| wcsncmp(new_path, L"//", 2) == 0); |
| if (!is_unc_like_path) { |
| env[1] = new_path[0]; |
| result = SetEnvironmentVariableW(env, new_path); |
| } |
| if (new_path != path_buf) |
| PyMem_RawFree(new_path); |
| return result ? TRUE : FALSE; |
| } |
| #endif |
| |
| #ifdef MS_WINDOWS |
| /* The CRT of Windows has a number of flaws wrt. its stat() implementation: |
| - time stamps are restricted to second resolution |
| - file modification times suffer from forth-and-back conversions between |
| UTC and local time |
| Therefore, we implement our own stat, based on the Win32 API directly. |
| */ |
| #define HAVE_STAT_NSEC 1 |
| #define HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES 1 |
| #define HAVE_STRUCT_STAT_ST_REPARSE_TAG 1 |
| |
| static void |
| find_data_to_file_info(WIN32_FIND_DATAW *pFileData, |
| BY_HANDLE_FILE_INFORMATION *info, |
| ULONG *reparse_tag) |
| { |
| memset(info, 0, sizeof(*info)); |
| info->dwFileAttributes = pFileData->dwFileAttributes; |
| info->ftCreationTime = pFileData->ftCreationTime; |
| info->ftLastAccessTime = pFileData->ftLastAccessTime; |
| info->ftLastWriteTime = pFileData->ftLastWriteTime; |
| info->nFileSizeHigh = pFileData->nFileSizeHigh; |
| info->nFileSizeLow = pFileData->nFileSizeLow; |
| /* info->nNumberOfLinks = 1; */ |
| if (pFileData->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) |
| *reparse_tag = pFileData->dwReserved0; |
| else |
| *reparse_tag = 0; |
| } |
| |
| static BOOL |
| attributes_from_dir(LPCWSTR pszFile, BY_HANDLE_FILE_INFORMATION *info, ULONG *reparse_tag) |
| { |
| HANDLE hFindFile; |
| WIN32_FIND_DATAW FileData; |
| LPCWSTR filename = pszFile; |
| size_t n = wcslen(pszFile); |
| if (n && (pszFile[n - 1] == L'\\' || pszFile[n - 1] == L'/')) { |
| // cannot use PyMem_Malloc here because we do not hold the GIL |
| filename = (LPCWSTR)malloc((n + 1) * sizeof(filename[0])); |
| if(!filename) { |
| SetLastError(ERROR_NOT_ENOUGH_MEMORY); |
| return FALSE; |
| } |
| wcsncpy_s((LPWSTR)filename, n + 1, pszFile, n); |
| while (--n > 0 && (filename[n] == L'\\' || filename[n] == L'/')) { |
| ((LPWSTR)filename)[n] = L'\0'; |
| } |
| if (!n || (n == 1 && filename[1] == L':')) { |
| // Nothing left to query |
| free((void *)filename); |
| return FALSE; |
| } |
| } |
| hFindFile = FindFirstFileW(filename, &FileData); |
| if (pszFile != filename) { |
| free((void *)filename); |
| } |
| if (hFindFile == INVALID_HANDLE_VALUE) { |
| return FALSE; |
| } |
| FindClose(hFindFile); |
| find_data_to_file_info(&FileData, info, reparse_tag); |
| return TRUE; |
| } |
| |
| |
| static void |
| update_st_mode_from_path(const wchar_t *path, DWORD attr, |
| struct _Py_stat_struct *result) |
| { |
| if (!(attr & FILE_ATTRIBUTE_DIRECTORY)) { |
| /* Fix the file execute permissions. This hack sets S_IEXEC if |
| the filename has an extension that is commonly used by files |
| that CreateProcessW can execute. A real implementation calls |
| GetSecurityInfo, OpenThreadToken/OpenProcessToken, and |
| AccessCheck to check for generic read, write, and execute |
| access. */ |
| const wchar_t *fileExtension = wcsrchr(path, '.'); |
| if (fileExtension) { |
| if (_wcsicmp(fileExtension, L".exe") == 0 || |
| _wcsicmp(fileExtension, L".bat") == 0 || |
| _wcsicmp(fileExtension, L".cmd") == 0 || |
| _wcsicmp(fileExtension, L".com") == 0) { |
| result->st_mode |= 0111; |
| } |
| } |
| } |
| } |
| |
| |
| static int |
| win32_xstat_slow_impl(const wchar_t *path, struct _Py_stat_struct *result, |
| BOOL traverse) |
| { |
| HANDLE hFile; |
| BY_HANDLE_FILE_INFORMATION fileInfo; |
| FILE_BASIC_INFO basicInfo; |
| FILE_ID_INFO idInfo; |
| FILE_ID_INFO *pIdInfo = &idInfo; |
| FILE_ATTRIBUTE_TAG_INFO tagInfo = { 0 }; |
| DWORD fileType, error; |
| BOOL isUnhandledTag = FALSE; |
| int retval = 0; |
| |
| DWORD access = FILE_READ_ATTRIBUTES; |
| DWORD flags = FILE_FLAG_BACKUP_SEMANTICS; /* Allow opening directories. */ |
| if (!traverse) { |
| flags |= FILE_FLAG_OPEN_REPARSE_POINT; |
| } |
| |
| hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING, flags, NULL); |
| if (hFile == INVALID_HANDLE_VALUE) { |
| /* Either the path doesn't exist, or the caller lacks access. */ |
| error = GetLastError(); |
| switch (error) { |
| case ERROR_ACCESS_DENIED: /* Cannot sync or read attributes. */ |
| case ERROR_SHARING_VIOLATION: /* It's a paging file. */ |
| /* Try reading the parent directory. */ |
| if (!attributes_from_dir(path, &fileInfo, &tagInfo.ReparseTag)) { |
| /* Cannot read the parent directory. */ |
| switch (GetLastError()) { |
| case ERROR_FILE_NOT_FOUND: /* File cannot be found */ |
| case ERROR_PATH_NOT_FOUND: /* File parent directory cannot be found */ |
| case ERROR_NOT_READY: /* Drive exists but unavailable */ |
| case ERROR_BAD_NET_NAME: /* Remote drive unavailable */ |
| break; |
| /* Restore the error from CreateFileW(). */ |
| default: |
| SetLastError(error); |
| } |
| |
| return -1; |
| } |
| if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { |
| if (traverse || |
| !IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { |
| /* The stat call has to traverse but cannot, so fail. */ |
| SetLastError(error); |
| return -1; |
| } |
| } |
| break; |
| |
| case ERROR_INVALID_PARAMETER: |
| /* \\.\con requires read or write access. */ |
| hFile = CreateFileW(path, access | GENERIC_READ, |
| FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, |
| OPEN_EXISTING, flags, NULL); |
| if (hFile == INVALID_HANDLE_VALUE) { |
| SetLastError(error); |
| return -1; |
| } |
| break; |
| |
| case ERROR_CANT_ACCESS_FILE: |
| /* bpo37834: open unhandled reparse points if traverse fails. */ |
| if (traverse) { |
| traverse = FALSE; |
| isUnhandledTag = TRUE; |
| hFile = CreateFileW(path, access, 0, NULL, OPEN_EXISTING, |
| flags | FILE_FLAG_OPEN_REPARSE_POINT, NULL); |
| } |
| if (hFile == INVALID_HANDLE_VALUE) { |
| SetLastError(error); |
| return -1; |
| } |
| break; |
| |
| default: |
| return -1; |
| } |
| } |
| |
| if (hFile != INVALID_HANDLE_VALUE) { |
| /* Handle types other than files on disk. */ |
| fileType = GetFileType(hFile); |
| if (fileType != FILE_TYPE_DISK) { |
| if (fileType == FILE_TYPE_UNKNOWN && GetLastError() != 0) { |
| retval = -1; |
| goto cleanup; |
| } |
| DWORD fileAttributes = GetFileAttributesW(path); |
| memset(result, 0, sizeof(*result)); |
| if (fileAttributes != INVALID_FILE_ATTRIBUTES && |
| fileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
| /* \\.\pipe\ or \\.\mailslot\ */ |
| result->st_mode = _S_IFDIR; |
| } else if (fileType == FILE_TYPE_CHAR) { |
| /* \\.\nul */ |
| result->st_mode = _S_IFCHR; |
| } else if (fileType == FILE_TYPE_PIPE) { |
| /* \\.\pipe\spam */ |
| result->st_mode = _S_IFIFO; |
| } |
| /* FILE_TYPE_UNKNOWN, e.g. \\.\mailslot\waitfor.exe\spam */ |
| goto cleanup; |
| } |
| |
| /* Query the reparse tag, and traverse a non-link. */ |
| if (!traverse) { |
| if (!GetFileInformationByHandleEx(hFile, FileAttributeTagInfo, |
| &tagInfo, sizeof(tagInfo))) { |
| /* Allow devices that do not support FileAttributeTagInfo. */ |
| switch (GetLastError()) { |
| case ERROR_INVALID_PARAMETER: |
| case ERROR_INVALID_FUNCTION: |
| case ERROR_NOT_SUPPORTED: |
| tagInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL; |
| tagInfo.ReparseTag = 0; |
| break; |
| default: |
| retval = -1; |
| goto cleanup; |
| } |
| } else if (tagInfo.FileAttributes & |
| FILE_ATTRIBUTE_REPARSE_POINT) { |
| if (IsReparseTagNameSurrogate(tagInfo.ReparseTag)) { |
| if (isUnhandledTag) { |
| /* Traversing previously failed for either this link |
| or its target. */ |
| SetLastError(ERROR_CANT_ACCESS_FILE); |
| retval = -1; |
| goto cleanup; |
| } |
| /* Traverse a non-link, but not if traversing already failed |
| for an unhandled tag. */ |
| } else if (!isUnhandledTag) { |
| CloseHandle(hFile); |
| return win32_xstat_slow_impl(path, result, TRUE); |
| } |
| } |
| } |
| |
| if (!GetFileInformationByHandle(hFile, &fileInfo) || |
| !GetFileInformationByHandleEx(hFile, FileBasicInfo, |
| &basicInfo, sizeof(basicInfo))) { |
| switch (GetLastError()) { |
| case ERROR_INVALID_PARAMETER: |
| case ERROR_INVALID_FUNCTION: |
| case ERROR_NOT_SUPPORTED: |
| /* Volumes and physical disks are block devices, e.g. |
| \\.\C: and \\.\PhysicalDrive0. */ |
| memset(result, 0, sizeof(*result)); |
| result->st_mode = 0x6000; /* S_IFBLK */ |
| goto cleanup; |
| } |
| retval = -1; |
| goto cleanup; |
| } |
| } |
| |
| if (!GetFileInformationByHandleEx(hFile, FileIdInfo, &idInfo, sizeof(idInfo))) { |
| /* Failed to get FileIdInfo, so do not pass it along */ |
| pIdInfo = NULL; |
| } |
| |
| _Py_attribute_data_to_stat(&fileInfo, tagInfo.ReparseTag, &basicInfo, pIdInfo, result); |
| update_st_mode_from_path(path, fileInfo.dwFileAttributes, result); |
| |
| cleanup: |
| if (hFile != INVALID_HANDLE_VALUE) { |
| /* Preserve last error if we are failing */ |
| error = retval ? GetLastError() : 0; |
| if (!CloseHandle(hFile)) { |
| retval = -1; |
| } else if (retval) { |
| /* Restore last error */ |
| SetLastError(error); |
| } |
| } |
| |
| return retval; |
| } |
| |
| static int |
| win32_xstat_impl(const wchar_t *path, struct _Py_stat_struct *result, |
| BOOL traverse) |
| { |
| FILE_STAT_BASIC_INFORMATION statInfo; |
| if (_Py_GetFileInformationByName(path, FileStatBasicByNameInfo, |
| &statInfo, sizeof(statInfo))) { |
| if (// Cannot use fast path for reparse points ... |
| !(statInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) |
| // ... unless it's a name surrogate (symlink) and we're not following |
| || (!traverse && IsReparseTagNameSurrogate(statInfo.ReparseTag)) |
| ) { |
| _Py_stat_basic_info_to_stat(&statInfo, result); |
| update_st_mode_from_path(path, statInfo.FileAttributes, result); |
| return 0; |
| } |
| } else { |
| switch(GetLastError()) { |
| case ERROR_FILE_NOT_FOUND: |
| case ERROR_PATH_NOT_FOUND: |
| case ERROR_NOT_READY: |
| case ERROR_BAD_NET_NAME: |
| /* These errors aren't worth retrying with the slow path */ |
| return -1; |
| case ERROR_NOT_SUPPORTED: |
| /* indicates the API couldn't be loaded */ |
| break; |
| } |
| } |
| |
| return win32_xstat_slow_impl(path, result, traverse); |
| } |
| |
| static int |
| win32_xstat(const wchar_t *path, struct _Py_stat_struct *result, BOOL traverse) |
| { |
| /* Protocol violation: we explicitly clear errno, instead of |
| setting it to a POSIX error. Callers should use GetLastError. */ |
| int code = win32_xstat_impl(path, result, traverse); |
| errno = 0; |
| |
| /* ctime is only deprecated from 3.12, so we copy birthtime across */ |
| result->st_ctime = result->st_birthtime; |
| result->st_ctime_nsec = result->st_birthtime_nsec; |
| return code; |
| } |
| /* About the following functions: win32_lstat_w, win32_stat, win32_stat_w |
| |
| In Posix, stat automatically traverses symlinks and returns the stat |
| structure for the target. In Windows, the equivalent GetFileAttributes by |
| default does not traverse symlinks and instead returns attributes for |
| the symlink. |
| |
| Instead, we will open the file (which *does* traverse symlinks by default) |
| and GetFileInformationByHandle(). */ |
| |
| static int |
| win32_lstat(const wchar_t* path, struct _Py_stat_struct *result) |
| { |
| return win32_xstat(path, result, FALSE); |
| } |
| |
| static int |
| win32_stat(const wchar_t* path, struct _Py_stat_struct *result) |
| { |
| return win32_xstat(path, result, TRUE); |
| } |
| |
| #endif /* MS_WINDOWS */ |
| |
| PyDoc_STRVAR(stat_result__doc__, |
| "stat_result: Result from stat, fstat, or lstat.\n\n\ |
| This object may be accessed either as a tuple of\n\ |
| (mode, ino, dev, nlink, uid, gid, size, atime, mtime, ctime)\n\ |
| or via the attributes st_mode, st_ino, st_dev, st_nlink, st_uid, and so on.\n\ |
| \n\ |
| Posix/windows: If your platform supports st_blksize, st_blocks, st_rdev,\n\ |
| or st_flags, they are available as attributes only.\n\ |
| \n\ |
| See os.stat for more information."); |
| |
| static PyStructSequence_Field stat_result_fields[] = { |
| {"st_mode", "protection bits"}, |
| {"st_ino", "inode"}, |
| {"st_dev", "device"}, |
| {"st_nlink", "number of hard links"}, |
| {"st_uid", "user ID of owner"}, |
| {"st_gid", "group ID of owner"}, |
| {"st_size", "total size, in bytes"}, |
| /* The NULL is replaced with PyStructSequence_UnnamedField later. */ |
| {NULL, "integer time of last access"}, |
| {NULL, "integer time of last modification"}, |
| {NULL, "integer time of last change"}, |
| {"st_atime", "time of last access"}, |
| {"st_mtime", "time of last modification"}, |
| {"st_ctime", "time of last change"}, |
| {"st_atime_ns", "time of last access in nanoseconds"}, |
| {"st_mtime_ns", "time of last modification in nanoseconds"}, |
| {"st_ctime_ns", "time of last change in nanoseconds"}, |
| #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE |
| {"st_blksize", "blocksize for filesystem I/O"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_BLOCKS |
| {"st_blocks", "number of blocks allocated"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_RDEV |
| {"st_rdev", "device type (if inode device)"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_FLAGS |
| {"st_flags", "user defined flags for file"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_GEN |
| {"st_gen", "generation number"}, |
| #endif |
| #if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS) |
| {"st_birthtime", "time of creation"}, |
| #endif |
| #ifdef MS_WINDOWS |
| {"st_birthtime_ns", "time of creation in nanoseconds"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES |
| {"st_file_attributes", "Windows file attribute bits"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_FSTYPE |
| {"st_fstype", "Type of filesystem"}, |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG |
| {"st_reparse_tag", "Windows reparse tag"}, |
| #endif |
| {0} |
| }; |
| |
| #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE |
| #define ST_BLKSIZE_IDX 16 |
| #else |
| #define ST_BLKSIZE_IDX 15 |
| #endif |
| |
| #ifdef HAVE_STRUCT_STAT_ST_BLOCKS |
| #define ST_BLOCKS_IDX (ST_BLKSIZE_IDX+1) |
| #else |
| #define ST_BLOCKS_IDX ST_BLKSIZE_IDX |
| #endif |
| |
| #ifdef HAVE_STRUCT_STAT_ST_RDEV |
| #define ST_RDEV_IDX (ST_BLOCKS_IDX+1) |
| #else |
| #define ST_RDEV_IDX ST_BLOCKS_IDX |
| #endif |
| |
| #ifdef HAVE_STRUCT_STAT_ST_FLAGS |
| #define ST_FLAGS_IDX (ST_RDEV_IDX+1) |
| #else |
| #define ST_FLAGS_IDX ST_RDEV_IDX |
| #endif |
| |
| #ifdef HAVE_STRUCT_STAT_ST_GEN |
| #define ST_GEN_IDX (ST_FLAGS_IDX+1) |
| #else |
| #define ST_GEN_IDX ST_FLAGS_IDX |
| #endif |
| |
| #if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) || defined(MS_WINDOWS) |
| #define ST_BIRTHTIME_IDX (ST_GEN_IDX+1) |
| #else |
| #define ST_BIRTHTIME_IDX ST_GEN_IDX |
| #endif |
| |
| #ifdef MS_WINDOWS |
| #define ST_BIRTHTIME_NS_IDX (ST_BIRTHTIME_IDX+1) |
| #else |
| #define ST_BIRTHTIME_NS_IDX ST_BIRTHTIME_IDX |
| #endif |
| |
| #if defined(HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES) || defined(MS_WINDOWS) |
| #define ST_FILE_ATTRIBUTES_IDX (ST_BIRTHTIME_NS_IDX+1) |
| #else |
| #define ST_FILE_ATTRIBUTES_IDX ST_BIRTHTIME_NS_IDX |
| #endif |
| |
| #ifdef HAVE_STRUCT_STAT_ST_FSTYPE |
| #define ST_FSTYPE_IDX (ST_FILE_ATTRIBUTES_IDX+1) |
| #else |
| #define ST_FSTYPE_IDX ST_FILE_ATTRIBUTES_IDX |
| #endif |
| |
| #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG |
| #define ST_REPARSE_TAG_IDX (ST_FSTYPE_IDX+1) |
| #else |
| #define ST_REPARSE_TAG_IDX ST_FSTYPE_IDX |
| #endif |
| |
| static PyStructSequence_Desc stat_result_desc = { |
| "stat_result", /* name */ |
| stat_result__doc__, /* doc */ |
| stat_result_fields, |
| 10 |
| }; |
| |
| PyDoc_STRVAR(statvfs_result__doc__, |
| "statvfs_result: Result from statvfs or fstatvfs.\n\n\ |
| This object may be accessed either as a tuple of\n\ |
| (bsize, frsize, blocks, bfree, bavail, files, ffree, favail, flag, namemax),\n\ |
| or via the attributes f_bsize, f_frsize, f_blocks, f_bfree, and so on.\n\ |
| \n\ |
| See os.statvfs for more information."); |
| |
| static PyStructSequence_Field statvfs_result_fields[] = { |
| {"f_bsize", }, |
| {"f_frsize", }, |
| {"f_blocks", }, |
| {"f_bfree", }, |
| {"f_bavail", }, |
| {"f_files", }, |
| {"f_ffree", }, |
| {"f_favail", }, |
| {"f_flag", }, |
| {"f_namemax",}, |
| {"f_fsid", }, |
| {0} |
| }; |
| |
| static PyStructSequence_Desc statvfs_result_desc = { |
| "statvfs_result", /* name */ |
| statvfs_result__doc__, /* doc */ |
| statvfs_result_fields, |
| 10 |
| }; |
| |
| #if defined(HAVE_WAITID) && !defined(__APPLE__) |
| PyDoc_STRVAR(waitid_result__doc__, |
| "waitid_result: Result from waitid.\n\n\ |
| This object may be accessed either as a tuple of\n\ |
| (si_pid, si_uid, si_signo, si_status, si_code),\n\ |
| or via the attributes si_pid, si_uid, and so on.\n\ |
| \n\ |
| See os.waitid for more information."); |
| |
| static PyStructSequence_Field waitid_result_fields[] = { |
| {"si_pid", }, |
| {"si_uid", }, |
| {"si_signo", }, |
| {"si_status", }, |
| {"si_code", }, |
| {0} |
| }; |
| |
| static PyStructSequence_Desc waitid_result_desc = { |
| "waitid_result", /* name */ |
| waitid_result__doc__, /* doc */ |
| waitid_result_fields, |
| 5 |
| }; |
| #endif |
| |
| static PyObject * |
| statresult_new(PyTypeObject *type, PyObject *args, PyObject *kwds) |
| { |
| PyStructSequence *result; |
| int i; |
| |
| // ht_module doesn't get set in PyStructSequence_NewType(), |
| // so we can't use PyType_GetModule(). |
| PyObject *mod = PyImport_GetModule(MODNAME_OBJ); |
| if (mod == NULL) { |
| return NULL; |
| } |
| _posixstate *state = get_posix_state(mod); |
| Py_DECREF(mod); |
| if (state == NULL) { |
| return NULL; |
| } |
| #define structseq_new state->statresult_new_orig |
| |
| result = (PyStructSequence*)structseq_new(type, args, kwds); |
| if (!result) |
| return NULL; |
| /* If we have been initialized from a tuple, |
| st_?time might be set to None. Initialize it |
| from the int slots. */ |
| for (i = 7; i <= 9; i++) { |
| if (result->ob_item[i+3] == Py_None) { |
| Py_DECREF(Py_None); |
| result->ob_item[i+3] = Py_NewRef(result->ob_item[i]); |
| } |
| } |
| return (PyObject*)result; |
| } |
| |
| static int |
| _posix_clear(PyObject *module) |
| { |
| _posixstate *state = get_posix_state(module); |
| Py_CLEAR(state->billion); |
| Py_CLEAR(state->DirEntryType); |
| Py_CLEAR(state->ScandirIteratorType); |
| #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) |
| Py_CLEAR(state->SchedParamType); |
| #endif |
| Py_CLEAR(state->StatResultType); |
| Py_CLEAR(state->StatVFSResultType); |
| Py_CLEAR(state->TerminalSizeType); |
| Py_CLEAR(state->TimesResultType); |
| Py_CLEAR(state->UnameResultType); |
| #if defined(HAVE_WAITID) && !defined(__APPLE__) |
| Py_CLEAR(state->WaitidResultType); |
| #endif |
| #if defined(HAVE_WAIT3) || defined(HAVE_WAIT4) |
| Py_CLEAR(state->struct_rusage); |
| #endif |
| Py_CLEAR(state->st_mode); |
| return 0; |
| } |
| |
| static int |
| _posix_traverse(PyObject *module, visitproc visit, void *arg) |
| { |
| _posixstate *state = get_posix_state(module); |
| Py_VISIT(state->billion); |
| Py_VISIT(state->DirEntryType); |
| Py_VISIT(state->ScandirIteratorType); |
| #if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) |
| Py_VISIT(state->SchedParamType); |
| #endif |
| Py_VISIT(state->StatResultType); |
| Py_VISIT(state->StatVFSResultType); |
| Py_VISIT(state->TerminalSizeType); |
| Py_VISIT(state->TimesResultType); |
| Py_VISIT(state->UnameResultType); |
| #if defined(HAVE_WAITID) && !defined(__APPLE__) |
| Py_VISIT(state->WaitidResultType); |
| #endif |
| #if defined(HAVE_WAIT3) || defined(HAVE_WAIT4) |
| Py_VISIT(state->struct_rusage); |
| #endif |
| Py_VISIT(state->st_mode); |
| return 0; |
| } |
| |
| static void |
| _posix_free(void *module) |
| { |
| _posix_clear((PyObject *)module); |
| } |
| |
| static void |
| fill_time(PyObject *module, PyObject *v, int s_index, int f_index, int ns_index, time_t sec, unsigned long nsec) |
| { |
| PyObject *s = _PyLong_FromTime_t(sec); |
| PyObject *ns_fractional = PyLong_FromUnsignedLong(nsec); |
| PyObject *s_in_ns = NULL; |
| PyObject *ns_total = NULL; |
| PyObject *float_s = NULL; |
| |
| if (!(s && ns_fractional)) |
| goto exit; |
| |
| s_in_ns = PyNumber_Multiply(s, get_posix_state(module)->billion); |
| if (!s_in_ns) |
| goto exit; |
| |
| ns_total = PyNumber_Add(s_in_ns, ns_fractional); |
| if (!ns_total) |
| goto exit; |
| |
| float_s = PyFloat_FromDouble(sec + 1e-9*nsec); |
| if (!float_s) { |
| goto exit; |
| } |
| |
| if (s_index >= 0) { |
| PyStructSequence_SET_ITEM(v, s_index, s); |
| s = NULL; |
| } |
| if (f_index >= 0) { |
| PyStructSequence_SET_ITEM(v, f_index, float_s); |
| float_s = NULL; |
| } |
| if (ns_index >= 0) { |
| PyStructSequence_SET_ITEM(v, ns_index, ns_total); |
| ns_total = NULL; |
| } |
| exit: |
| Py_XDECREF(s); |
| Py_XDECREF(ns_fractional); |
| Py_XDECREF(s_in_ns); |
| Py_XDECREF(ns_total); |
| Py_XDECREF(float_s); |
| } |
| |
| #ifdef MS_WINDOWS |
| static PyObject* |
| _pystat_l128_from_l64_l64(uint64_t low, uint64_t high) |
| { |
| PyObject *o_low = PyLong_FromUnsignedLongLong(low); |
| if (!o_low || !high) { |
| return o_low; |
| } |
| PyObject *o_high = PyLong_FromUnsignedLongLong(high); |
| PyObject *l64 = o_high ? PyLong_FromLong(64) : NULL; |
| if (!l64) { |
| Py_XDECREF(o_high); |
| Py_DECREF(o_low); |
| return NULL; |
| } |
| Py_SETREF(o_high, PyNumber_Lshift(o_high, l64)); |
| Py_DECREF(l64); |
| if (!o_high) { |
| Py_DECREF(o_low); |
| return NULL; |
| } |
| Py_SETREF(o_low, PyNumber_Add(o_low, o_high)); |
| Py_DECREF(o_high); |
| return o_low; |
| } |
| #endif |
| |
| /* pack a system stat C structure into the Python stat tuple |
| (used by posix_stat() and posix_fstat()) */ |
| static PyObject* |
| _pystat_fromstructstat(PyObject *module, STRUCT_STAT *st) |
| { |
| unsigned long ansec, mnsec, cnsec; |
| PyObject *StatResultType = get_posix_state(module)->StatResultType; |
| PyObject *v = PyStructSequence_New((PyTypeObject *)StatResultType); |
| if (v == NULL) |
| return NULL; |
| |
| PyStructSequence_SET_ITEM(v, 0, PyLong_FromLong((long)st->st_mode)); |
| #ifdef MS_WINDOWS |
| PyStructSequence_SET_ITEM(v, 1, _pystat_l128_from_l64_l64(st->st_ino, st->st_ino_high)); |
| PyStructSequence_SET_ITEM(v, 2, PyLong_FromUnsignedLongLong(st->st_dev)); |
| #else |
| static_assert(sizeof(unsigned long long) >= sizeof(st->st_ino), |
| "stat.st_ino is larger than unsigned long long"); |
| PyStructSequence_SET_ITEM(v, 1, PyLong_FromUnsignedLongLong(st->st_ino)); |
| PyStructSequence_SET_ITEM(v, 2, _PyLong_FromDev(st->st_dev)); |
| #endif |
| PyStructSequence_SET_ITEM(v, 3, PyLong_FromLong((long)st->st_nlink)); |
| #if defined(MS_WINDOWS) |
| PyStructSequence_SET_ITEM(v, 4, PyLong_FromLong(0)); |
| PyStructSequence_SET_ITEM(v, 5, PyLong_FromLong(0)); |
| #else |
| PyStructSequence_SET_ITEM(v, 4, _PyLong_FromUid(st->st_uid)); |
| PyStructSequence_SET_ITEM(v, 5, _PyLong_FromGid(st->st_gid)); |
| #endif |
| static_assert(sizeof(long long) >= sizeof(st->st_size), |
| "stat.st_size is larger than long long"); |
| PyStructSequence_SET_ITEM(v, 6, PyLong_FromLongLong(st->st_size)); |
| |
| #if defined(HAVE_STAT_TV_NSEC) |
| ansec = st->st_atim.tv_nsec; |
| mnsec = st->st_mtim.tv_nsec; |
| cnsec = st->st_ctim.tv_nsec; |
| #elif defined(HAVE_STAT_TV_NSEC2) |
| ansec = st->st_atimespec.tv_nsec; |
| mnsec = st->st_mtimespec.tv_nsec; |
| cnsec = st->st_ctimespec.tv_nsec; |
| #elif defined(HAVE_STAT_NSEC) |
| ansec = st->st_atime_nsec; |
| mnsec = st->st_mtime_nsec; |
| cnsec = st->st_ctime_nsec; |
| #else |
| ansec = mnsec = cnsec = 0; |
| #endif |
| fill_time(module, v, 7, 10, 13, st->st_atime, ansec); |
| fill_time(module, v, 8, 11, 14, st->st_mtime, mnsec); |
| fill_time(module, v, 9, 12, 15, st->st_ctime, cnsec); |
| |
| #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE |
| PyStructSequence_SET_ITEM(v, ST_BLKSIZE_IDX, |
| PyLong_FromLong((long)st->st_blksize)); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_BLOCKS |
| PyStructSequence_SET_ITEM(v, ST_BLOCKS_IDX, |
| PyLong_FromLong((long)st->st_blocks)); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_RDEV |
| PyStructSequence_SET_ITEM(v, ST_RDEV_IDX, |
| PyLong_FromLong((long)st->st_rdev)); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_GEN |
| PyStructSequence_SET_ITEM(v, ST_GEN_IDX, |
| PyLong_FromLong((long)st->st_gen)); |
| #endif |
| #if defined(HAVE_STRUCT_STAT_ST_BIRTHTIME) |
| { |
| PyObject *val; |
| unsigned long bsec,bnsec; |
| bsec = (long)st->st_birthtime; |
| #ifdef HAVE_STAT_TV_NSEC2 |
| bnsec = st->st_birthtimespec.tv_nsec; |
| #else |
| bnsec = 0; |
| #endif |
| val = PyFloat_FromDouble(bsec + 1e-9*bnsec); |
| PyStructSequence_SET_ITEM(v, ST_BIRTHTIME_IDX, |
| val); |
| } |
| #elif defined(MS_WINDOWS) |
| fill_time(module, v, -1, ST_BIRTHTIME_IDX, ST_BIRTHTIME_NS_IDX, |
| st->st_birthtime, st->st_birthtime_nsec); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_FLAGS |
| PyStructSequence_SET_ITEM(v, ST_FLAGS_IDX, |
| PyLong_FromLong((long)st->st_flags)); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_FILE_ATTRIBUTES |
| PyStructSequence_SET_ITEM(v, ST_FILE_ATTRIBUTES_IDX, |
| PyLong_FromUnsignedLong(st->st_file_attributes)); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_FSTYPE |
| PyStructSequence_SET_ITEM(v, ST_FSTYPE_IDX, |
| PyUnicode_FromString(st->st_fstype)); |
| #endif |
| #ifdef HAVE_STRUCT_STAT_ST_REPARSE_TAG |
| PyStructSequence_SET_ITEM(v, ST_REPARSE_TAG_IDX, |
| PyLong_FromUnsignedLong(st->st_reparse_tag)); |
| #endif |
| |
| if (PyErr_Occurred()) { |
| Py_DECREF(v); |
| return NULL; |
| } |
| |
| return v; |
| } |
| |
| /* POSIX methods */ |
| |
| |
| static PyObject * |
| posix_do_stat(PyObject *module, const char *function_name, path_t *path, |
| int dir_fd, int follow_symlinks) |
| { |
| STRUCT_STAT st; |
| int result; |
| |
| #ifdef HAVE_FSTATAT |
| int fstatat_unavailable = 0; |
| #endif |
| |
| #if !defined(MS_WINDOWS) && !defined(HAVE_FSTATAT) && !defined(HAVE_LSTAT) |
| if (follow_symlinks_specified(function_name, follow_symlinks)) |
| return NULL; |
| #endif |
| |
| if (path_and_dir_fd_invalid("stat", path, dir_fd) || |
| dir_fd_and_fd_invalid("stat", dir_fd, path->fd) || |
| fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks)) |
| return NULL; |
| |
| Py_BEGIN_ALLOW_THREADS |
| if (path->fd != -1) |
| result = FSTAT(path->fd, &st); |
| #ifdef MS_WINDOWS |
| else if (follow_symlinks) |
| result = win32_stat(path->wide, &st); |
| else |
| result = win32_lstat(path->wide, &st); |
| #else |
| else |
| #if defined(HAVE_LSTAT) |
| if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) |
| result = LSTAT(path->narrow, &st); |
| else |
| #endif /* HAVE_LSTAT */ |
| #ifdef HAVE_FSTATAT |
| if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks) { |
| if (HAVE_FSTATAT_RUNTIME) { |
| result = fstatat(dir_fd, path->narrow, &st, |
| follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); |
| |
| } else { |
| fstatat_unavailable = 1; |
| } |
| } else |
| #endif /* HAVE_FSTATAT */ |
| result = STAT(path->narrow, &st); |
| #endif /* MS_WINDOWS */ |
| Py_END_ALLOW_THREADS |
| |
| #ifdef HAVE_FSTATAT |
| if (fstatat_unavailable) { |
| argument_unavailable_error("stat", "dir_fd"); |
| return NULL; |
| } |
| #endif |
| |
| if (result != 0) { |
| return path_error(path); |
| } |
| |
| return _pystat_fromstructstat(module, &st); |
| } |
| |
| /*[python input] |
| |
| for s in """ |
| |
| FACCESSAT |
| FCHMODAT |
| FCHOWNAT |
| FSTATAT |
| LINKAT |
| MKDIRAT |
| MKFIFOAT |
| MKNODAT |
| OPENAT |
| READLINKAT |
| SYMLINKAT |
| UNLINKAT |
| |
| """.strip().split(): |
| s = s.strip() |
| print(""" |
| #ifdef HAVE_{s} |
| #define {s}_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define {s}_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| """.rstrip().format(s=s)) |
| |
| for s in """ |
| |
| FCHDIR |
| FCHMOD |
| FCHOWN |
| FDOPENDIR |
| FEXECVE |
| FPATHCONF |
| FSTATVFS |
| FTRUNCATE |
| |
| """.strip().split(): |
| s = s.strip() |
| print(""" |
| #ifdef HAVE_{s} |
| #define PATH_HAVE_{s} 1 |
| #else |
| #define PATH_HAVE_{s} 0 |
| #endif |
| |
| """.rstrip().format(s=s)) |
| [python start generated code]*/ |
| |
| #ifdef HAVE_FACCESSAT |
| #define FACCESSAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define FACCESSAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_FCHMODAT |
| #define FCHMODAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define FCHMODAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_FCHOWNAT |
| #define FCHOWNAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define FCHOWNAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_FSTATAT |
| #define FSTATAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define FSTATAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_LINKAT |
| #define LINKAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define LINKAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_MKDIRAT |
| #define MKDIRAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define MKDIRAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_MKFIFOAT |
| #define MKFIFOAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define MKFIFOAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_MKNODAT |
| #define MKNODAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define MKNODAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_OPENAT |
| #define OPENAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define OPENAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_READLINKAT |
| #define READLINKAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define READLINKAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_SYMLINKAT |
| #define SYMLINKAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define SYMLINKAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_UNLINKAT |
| #define UNLINKAT_DIR_FD_CONVERTER dir_fd_converter |
| #else |
| #define UNLINKAT_DIR_FD_CONVERTER dir_fd_unavailable |
| #endif |
| |
| #ifdef HAVE_FCHDIR |
| #define PATH_HAVE_FCHDIR 1 |
| #else |
| #define PATH_HAVE_FCHDIR 0 |
| #endif |
| |
| #ifdef HAVE_FCHMOD |
| #define PATH_HAVE_FCHMOD 1 |
| #else |
| #define PATH_HAVE_FCHMOD 0 |
| #endif |
| |
| #ifdef HAVE_FCHOWN |
| #define PATH_HAVE_FCHOWN 1 |
| #else |
| #define PATH_HAVE_FCHOWN 0 |
| #endif |
| |
| #ifdef HAVE_FDOPENDIR |
| #define PATH_HAVE_FDOPENDIR 1 |
| #else |
| #define PATH_HAVE_FDOPENDIR 0 |
| #endif |
| |
| #ifdef HAVE_FEXECVE |
| #define PATH_HAVE_FEXECVE 1 |
| #else |
| #define PATH_HAVE_FEXECVE 0 |
| #endif |
| |
| #ifdef HAVE_FPATHCONF |
| #define PATH_HAVE_FPATHCONF 1 |
| #else |
| #define PATH_HAVE_FPATHCONF 0 |
| #endif |
| |
| #ifdef HAVE_FSTATVFS |
| #define PATH_HAVE_FSTATVFS 1 |
| #else |
| #define PATH_HAVE_FSTATVFS 0 |
| #endif |
| |
| #ifdef HAVE_FTRUNCATE |
| #define PATH_HAVE_FTRUNCATE 1 |
| #else |
| #define PATH_HAVE_FTRUNCATE 0 |
| #endif |
| /*[python end generated code: output=4bd4f6f7d41267f1 input=80b4c890b6774ea5]*/ |
| |
| #ifdef MS_WINDOWS |
| #undef PATH_HAVE_FTRUNCATE |
| #define PATH_HAVE_FTRUNCATE 1 |
| #endif |
| |
| /*[python input] |
| |
| class path_t_converter(CConverter): |
| |
| type = "path_t" |
| impl_by_reference = True |
| parse_by_reference = True |
| |
| converter = 'path_converter' |
| |
| def converter_init(self, *, allow_fd=False, nullable=False): |
| # right now path_t doesn't support default values. |
| # to support a default value, you'll need to override initialize(). |
| if self.default not in (unspecified, None): |
| fail("Can't specify a default to the path_t converter!") |
| |
| if self.c_default not in (None, 'Py_None'): |
| raise RuntimeError("Can't specify a c_default to the path_t converter!") |
| |
| self.nullable = nullable |
| self.allow_fd = allow_fd |
| |
| def pre_render(self): |
| def strify(value): |
| if isinstance(value, str): |
| return value |
| return str(int(bool(value))) |
| |
| # add self.py_name here when merging with posixmodule conversion |
| self.c_default = 'PATH_T_INITIALIZE("{}", "{}", {}, {})'.format( |
| self.function.name, |
| self.name, |
| strify(self.nullable), |
| strify(self.allow_fd), |
| ) |
| |
| def cleanup(self): |
| return "path_cleanup(&" + self.name + ");\n" |
| |
| |
| class dir_fd_converter(CConverter): |
| type = 'int' |
| |
| def converter_init(self, requires=None): |
| if self.default in (unspecified, None): |
| self.c_default = 'DEFAULT_DIR_FD' |
| if isinstance(requires, str): |
| self.converter = requires.upper() + '_DIR_FD_CONVERTER' |
| else: |
| self.converter = 'dir_fd_converter' |
| |
| class uid_t_converter(CConverter): |
| type = "uid_t" |
| converter = '_Py_Uid_Converter' |
| |
| class gid_t_converter(CConverter): |
| type = "gid_t" |
| converter = '_Py_Gid_Converter' |
| |
| class dev_t_converter(CConverter): |
| type = 'dev_t' |
| converter = '_Py_Dev_Converter' |
| |
| class dev_t_return_converter(unsigned_long_return_converter): |
| type = 'dev_t' |
| conversion_fn = '_PyLong_FromDev' |
| unsigned_cast = '(dev_t)' |
| |
| class FSConverter_converter(CConverter): |
| type = 'PyObject *' |
| converter = 'PyUnicode_FSConverter' |
| def converter_init(self): |
| if self.default is not unspecified: |
| fail("FSConverter_converter does not support default values") |
| self.c_default = 'NULL' |
| |
| def cleanup(self): |
| return "Py_XDECREF(" + self.name + ");\n" |
| |
| class pid_t_converter(CConverter): |
| type = 'pid_t' |
| format_unit = '" _Py_PARSE_PID "' |
| |
| class idtype_t_converter(int_converter): |
| type = 'idtype_t' |
| |
| class id_t_converter(CConverter): |
| type = 'id_t' |
| format_unit = '" _Py_PARSE_PID "' |
| |
| class intptr_t_converter(CConverter): |
| type = 'intptr_t' |
| format_unit = '" _Py_PARSE_INTPTR "' |
| |
| class Py_off_t_converter(CConverter): |
| type = 'Py_off_t' |
| converter = 'Py_off_t_converter' |
| |
| class Py_off_t_return_converter(long_return_converter): |
| type = 'Py_off_t' |
| conversion_fn = 'PyLong_FromPy_off_t' |
| |
| class path_confname_converter(CConverter): |
| type="int" |
| converter="conv_path_confname" |
| |
| class confstr_confname_converter(path_confname_converter): |
| converter='conv_confstr_confname' |
| |
| class sysconf_confname_converter(path_confname_converter): |
| converter="conv_sysconf_confname" |
| |
| [python start generated code]*/ |
| /*[python end generated code: output=da39a3ee5e6b4b0d input=3338733161aa7879]*/ |
| |
| /*[clinic input] |
| |
| os.stat |
| |
| path : path_t(allow_fd=True) |
| Path to be examined; can be string, bytes, a path-like object or |
| open-file-descriptor int. |
| |
| * |
| |
| dir_fd : dir_fd(requires='fstatat') = None |
| If not None, it should be a file descriptor open to a directory, |
| and path should be a relative string; path will then be relative to |
| that directory. |
| |
| follow_symlinks: bool = True |
| If False, and the last element of the path is a symbolic link, |
| stat will examine the symbolic link itself instead of the file |
| the link points to. |
| |
| Perform a stat system call on the given path. |
| |
| dir_fd and follow_symlinks may not be implemented |
| on your platform. If they are unavailable, using them will raise a |
| NotImplementedError. |
| |
| It's an error to use dir_fd or follow_symlinks when specifying path as |
| an open file descriptor. |
| |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_stat_impl(PyObject *module, path_t *path, int dir_fd, int follow_symlinks) |
| /*[clinic end generated code: output=7d4976e6f18a59c5 input=01d362ebcc06996b]*/ |
| { |
| return posix_do_stat(module, "stat", path, dir_fd, follow_symlinks); |
| } |
| |
| |
| /*[clinic input] |
| os.lstat |
| |
| path : path_t |
| |
| * |
| |
| dir_fd : dir_fd(requires='fstatat') = None |
| |
| Perform a stat system call on the given path, without following symbolic links. |
| |
| Like stat(), but do not follow symbolic links. |
| Equivalent to stat(path, follow_symlinks=False). |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_lstat_impl(PyObject *module, path_t *path, int dir_fd) |
| /*[clinic end generated code: output=ef82a5d35ce8ab37 input=0b7474765927b925]*/ |
| { |
| int follow_symlinks = 0; |
| return posix_do_stat(module, "lstat", path, dir_fd, follow_symlinks); |
| } |
| |
| |
| /*[clinic input] |
| os.access -> bool |
| |
| path: path_t |
| Path to be tested; can be string, bytes, or a path-like object. |
| |
| mode: int |
| Operating-system mode bitfield. Can be F_OK to test existence, |
| or the inclusive-OR of R_OK, W_OK, and X_OK. |
| |
| * |
| |
| dir_fd : dir_fd(requires='faccessat') = None |
| If not None, it should be a file descriptor open to a directory, |
| and path should be relative; path will then be relative to that |
| directory. |
| |
| effective_ids: bool = False |
| If True, access will use the effective uid/gid instead of |
| the real uid/gid. |
| |
| follow_symlinks: bool = True |
| If False, and the last element of the path is a symbolic link, |
| access will examine the symbolic link itself instead of the file |
| the link points to. |
| |
| Use the real uid/gid to test for access to a path. |
| |
| {parameters} |
| dir_fd, effective_ids, and follow_symlinks may not be implemented |
| on your platform. If they are unavailable, using them will raise a |
| NotImplementedError. |
| |
| Note that most operations will use the effective uid/gid, therefore this |
| routine can be used in a suid/sgid environment to test if the invoking user |
| has the specified access to the path. |
| |
| [clinic start generated code]*/ |
| |
| static int |
| os_access_impl(PyObject *module, path_t *path, int mode, int dir_fd, |
| int effective_ids, int follow_symlinks) |
| /*[clinic end generated code: output=cf84158bc90b1a77 input=3ffe4e650ee3bf20]*/ |
| { |
| int return_value; |
| |
| #ifdef MS_WINDOWS |
| DWORD attr; |
| #else |
| int result; |
| #endif |
| |
| #ifdef HAVE_FACCESSAT |
| int faccessat_unavailable = 0; |
| #endif |
| |
| #ifndef HAVE_FACCESSAT |
| if (follow_symlinks_specified("access", follow_symlinks)) |
| return -1; |
| |
| if (effective_ids) { |
| argument_unavailable_error("access", "effective_ids"); |
| return -1; |
| } |
| #endif |
| |
| #ifdef MS_WINDOWS |
| Py_BEGIN_ALLOW_THREADS |
| attr = GetFileAttributesW(path->wide); |
| Py_END_ALLOW_THREADS |
| |
| /* |
| * Access is possible if |
| * * we didn't get a -1, and |
| * * write access wasn't requested, |
| * * or the file isn't read-only, |
| * * or it's a directory. |
| * (Directories cannot be read-only on Windows.) |
| */ |
| return_value = (attr != INVALID_FILE_ATTRIBUTES) && |
| (!(mode & 2) || |
| !(attr & FILE_ATTRIBUTE_READONLY) || |
| (attr & FILE_ATTRIBUTE_DIRECTORY)); |
| #else |
| |
| Py_BEGIN_ALLOW_THREADS |
| #ifdef HAVE_FACCESSAT |
| if ((dir_fd != DEFAULT_DIR_FD) || |
| effective_ids || |
| !follow_symlinks) { |
| |
| if (HAVE_FACCESSAT_RUNTIME) { |
| int flags = 0; |
| if (!follow_symlinks) |
| flags |= AT_SYMLINK_NOFOLLOW; |
| if (effective_ids) |
| flags |= AT_EACCESS; |
| result = faccessat(dir_fd, path->narrow, mode, flags); |
| } else { |
| faccessat_unavailable = 1; |
| } |
| } |
| else |
| #endif |
| result = access(path->narrow, mode); |
| Py_END_ALLOW_THREADS |
| |
| #ifdef HAVE_FACCESSAT |
| if (faccessat_unavailable) { |
| if (dir_fd != DEFAULT_DIR_FD) { |
| argument_unavailable_error("access", "dir_fd"); |
| return -1; |
| } |
| if (follow_symlinks_specified("access", follow_symlinks)) |
| return -1; |
| |
| if (effective_ids) { |
| argument_unavailable_error("access", "effective_ids"); |
| return -1; |
| } |
| /* should be unreachable */ |
| return -1; |
| } |
| #endif |
| return_value = !result; |
| #endif |
| |
| return return_value; |
| } |
| |
| #ifndef F_OK |
| #define F_OK 0 |
| #endif |
| #ifndef R_OK |
| #define R_OK 4 |
| #endif |
| #ifndef W_OK |
| #define W_OK 2 |
| #endif |
| #ifndef X_OK |
| #define X_OK 1 |
| #endif |
| |
| |
| #ifdef HAVE_TTYNAME |
| /*[clinic input] |
| os.ttyname |
| |
| fd: int |
| Integer file descriptor handle. |
| |
| / |
| |
| Return the name of the terminal device connected to 'fd'. |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_ttyname_impl(PyObject *module, int fd) |
| /*[clinic end generated code: output=c424d2e9d1cd636a input=9ff5a58b08115c55]*/ |
| { |
| |
| long size = sysconf(_SC_TTY_NAME_MAX); |
| if (size == -1) { |
| return posix_error(); |
| } |
| char *buffer = (char *)PyMem_RawMalloc(size); |
| if (buffer == NULL) { |
| return PyErr_NoMemory(); |
| } |
| int ret = ttyname_r(fd, buffer, size); |
| if (ret != 0) { |
| PyMem_RawFree(buffer); |
| errno = ret; |
| return posix_error(); |
| } |
| PyObject *res = PyUnicode_DecodeFSDefault(buffer); |
| PyMem_RawFree(buffer); |
| return res; |
| } |
| #endif |
| |
| #ifdef HAVE_CTERMID |
| /*[clinic input] |
| os.ctermid |
| |
| Return the name of the controlling terminal for this process. |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_ctermid_impl(PyObject *module) |
| /*[clinic end generated code: output=02f017e6c9e620db input=3b87fdd52556382d]*/ |
| { |
| char *ret; |
| char buffer[L_ctermid]; |
| |
| #ifdef USE_CTERMID_R |
| ret = ctermid_r(buffer); |
| #else |
| ret = ctermid(buffer); |
| #endif |
| if (ret == NULL) |
| return posix_error(); |
| return PyUnicode_DecodeFSDefault(buffer); |
| } |
| #endif /* HAVE_CTERMID */ |
| |
| |
| /*[clinic input] |
| os.chdir |
| |
| path: path_t(allow_fd='PATH_HAVE_FCHDIR') |
| |
| Change the current working directory to the specified path. |
| |
| path may always be specified as a string. |
| On some platforms, path may also be specified as an open file descriptor. |
| If this functionality is unavailable, using it raises an exception. |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_chdir_impl(PyObject *module, path_t *path) |
| /*[clinic end generated code: output=3be6400eee26eaae input=1a4a15b4d12cb15d]*/ |
| { |
| int result; |
| |
| if (PySys_Audit("os.chdir", "(O)", path->object) < 0) { |
| return NULL; |
| } |
| |
| Py_BEGIN_ALLOW_THREADS |
| #ifdef MS_WINDOWS |
| /* on unix, success = 0, on windows, success = !0 */ |
| result = !win32_wchdir(path->wide); |
| #else |
| #ifdef HAVE_FCHDIR |
| if (path->fd != -1) |
| result = fchdir(path->fd); |
| else |
| #endif |
| result = chdir(path->narrow); |
| #endif |
| Py_END_ALLOW_THREADS |
| |
| if (result) { |
| return path_error(path); |
| } |
| |
| Py_RETURN_NONE; |
| } |
| |
| |
| #ifdef HAVE_FCHDIR |
| /*[clinic input] |
| os.fchdir |
| |
| fd: fildes |
| |
| Change to the directory of the given file descriptor. |
| |
| fd must be opened on a directory, not a file. |
| Equivalent to os.chdir(fd). |
| |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_fchdir_impl(PyObject *module, int fd) |
| /*[clinic end generated code: output=42e064ec4dc00ab0 input=18e816479a2fa985]*/ |
| { |
| if (PySys_Audit("os.chdir", "(i)", fd) < 0) { |
| return NULL; |
| } |
| return posix_fildes_fd(fd, fchdir); |
| } |
| #endif /* HAVE_FCHDIR */ |
| |
| |
| /*[clinic input] |
| os.chmod |
| |
| path: path_t(allow_fd='PATH_HAVE_FCHMOD') |
| Path to be modified. May always be specified as a str, bytes, or a path-like object. |
| On some platforms, path may also be specified as an open file descriptor. |
| If this functionality is unavailable, using it raises an exception. |
| |
| mode: int |
| Operating-system mode bitfield. |
| Be careful when using number literals for *mode*. The conventional UNIX notation for |
| numeric modes uses an octal base, which needs to be indicated with a ``0o`` prefix in |
| Python. |
| |
| * |
| |
| dir_fd : dir_fd(requires='fchmodat') = None |
| If not None, it should be a file descriptor open to a directory, |
| and path should be relative; path will then be relative to that |
| directory. |
| |
| follow_symlinks: bool = True |
| If False, and the last element of the path is a symbolic link, |
| chmod will modify the symbolic link itself instead of the file |
| the link points to. |
| |
| Change the access permissions of a file. |
| |
| It is an error to use dir_fd or follow_symlinks when specifying path as |
| an open file descriptor. |
| dir_fd and follow_symlinks may not be implemented on your platform. |
| If they are unavailable, using them will raise a NotImplementedError. |
| |
| [clinic start generated code]*/ |
| |
| static PyObject * |
| os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd, |
| int follow_symlinks) |
| /*[clinic end generated code: output=5cf6a94915cc7bff input=674a14bc998de09d]*/ |
| { |
| int result; |
| |
| #ifdef MS_WINDOWS |
| DWORD attr; |
| #endif |
| |
| #ifdef HAVE_FCHMODAT |
| int fchmodat_nofollow_unsupported = 0; |
| int fchmodat_unsupported = 0; |
| #endif |
| |
| #if !(defined(HAVE_FCHMODAT) || defined(HAVE_LCHMOD)) |
| if (follow_symlinks_specified("chmod", follow_symlinks)) |
| return NULL; |
| #endif |
| |
| if (PySys_Audit("os.chmod", "Oii", path->object, mode, |
| dir_fd == DEFAULT_DIR_FD ? -1 : dir_fd) < 0) { |
| return NULL; |
| } |
| |
| #ifdef MS_WINDOWS |
| Py_BEGIN_ALLOW_THREADS |
| attr = GetFileAttributesW(path->wide); |
| if (attr == INVALID_FILE_ATTRIBUTES) |
| result = 0; |
| else { |
| if (mode & _S_IWRITE) |
| attr &= ~FILE_ATTRIBUTE_READONLY; |
| else |
| attr |= FILE_ATTRIBUTE_READONLY; |
| result = SetFileAttributesW(path->wide, attr); |
| } |
| Py_END_ALLOW_THREADS |
| |
| if (!result) { |
| return path_error(path); |
| } |
| #else /* MS_WINDOWS */ |
| Py_BEGIN_ALLOW_THREADS |
| #ifdef HAVE_FCHMOD |
| if (path->fd != -1) |
| result = fchmod(path->fd, mode); |
| else |
| #endif /* HAVE_CHMOD */ |
| #ifdef HAVE_LCHMOD |
| if ((!follow_symlinks) && (dir_fd == DEFAULT_DIR_FD)) |
| result = lchmod(path->narrow, mode); |
| else |
| #endif /* HAVE_LCHMOD */ |
| #ifdef HAVE_FCHMODAT |
| if ((dir_fd != DEFAULT_DIR_FD) || !follow_symlinks) { |
| if (HAVE_FCHMODAT_RUNTIME) { |
| /* |
| * fchmodat() doesn't currently support AT_SYMLINK_NOFOLLOW! |
| * The documentation specifically shows how to use it, |
| * and then says it isn't implemented yet. |
| * (true on linux with glibc 2.15, and openindiana 3.x) |
| * |
| * Once it is supported, os.chmod will automatically |
| * support dir_fd and follow_symlinks=False. (Hopefully.) |
| * Until then, we need to be careful what exception we raise. |
| */ |
| result = fchmodat(dir_fd, path->narrow, mode, |
| follow_symlinks ? 0 : AT_SYMLINK_NOFOLLOW); |
| /* |
| * But wait! We can't throw the exception without allowing threads, |
| * and we can't do that in this nested scope. (Macro trickery, sigh.) |
| */ |
| fchmodat_nofollow_unsupported = |
| result && |
| ((errno == ENOTSUP) || (errno == EOPNOTSUPP)) && |
| !follow_symlinks; |
| } else { |
| fchmodat_unsupported = 1; |
| fchmodat_nofollow_unsupported = 1; |
| |
| result = -1; |
| } |
| } |
| else |
| #endif /* HAVE_FHCMODAT */ |
| { |
| #ifdef HAVE_CHMOD |
| result = chmod(path->narrow, mode); |
| #elif defined(__wasi__) |
| // WASI SDK 15.0 does not support chmod. |
| // Ignore missing syscall for now. |
| result = 0; |
| #else |
| result = -1; |
| errno = ENOSYS; |
| #endif |
| } |
| Py_END_ALLOW_THREADS |
| |
| if (result) { |
| #ifdef HAVE_FCHMODAT |
| if (fchmodat_unsupported) { |
| if (dir_fd != DEFAULT_DIR_FD) { |
| argument_unavailable_error("chmod", "dir_fd"); |
| return NULL; |
| } |
| } |
| |
| if (fchmodat_nofollow_unsupported) { |
| if (dir_fd != DEFAULT_DIR_FD) |
| dir_fd_and_follow_symlinks_invalid("chmod", |
| dir_fd, follow_symlinks); |
| else |
| follow_symlinks_specified("chmod", follow_symlinks); |
| return NULL; |
| } |
| else |
| #endif /* HAVE_FCHMODAT */ |
| return path_error(path); |
| } |
| #endif /* MS_WINDOWS */ |
| |
| Py_RETURN_NONE; |
| } |
| |
| |