[issue47203] ImportError: DLL load failed while importing binascii: %1 is not a valid Win32 application.

2022-04-05 Thread Eryk Sun


Eryk Sun  added the comment:

There is something fundamentally wrong with the way modules built into the 
interpreter DLL (python3x.dll) are loaded if anything in sys.path or the system 
PATH can cause an import to fail.

--

___
Python tracker 
<https://bugs.python.org/issue47203>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47203] ImportError: DLL load failed while importing binascii: %1 is not a valid Win32 application.

2022-04-03 Thread Eryk Sun


Eryk Sun  added the comment:

The user site packages directory is architecture specific starting with 3.10, 
e.g. "%AppData%\Python\Python310-32\site-packages". The PYTHONPATH environment 
variable is shared by all versions. 

However, I don't understand how the binascii module could be a problem because 
it's built into the interpreter DLL itself, i.e. it's in 
sys.builtin_module_names. There is no binascii.pyd, and, even if there were, it 
would be ignored.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47203>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47198] os.stat on windows doesn't take an open file even though os.stat in os.supports_fd

2022-04-01 Thread Eryk Sun


Eryk Sun  added the comment:

You're mistaken about what `fd` is. It's a TextIOWrapper, which wraps a 
BufferedWriter, which buffers a FileIO raw file object, which accesses the open 
file number fd.fileno(). For example:

>>> f = open('tmp.tmp','w')
>>> os.stat(f.fileno()).st_size
0

--
nosy: +eryksun
resolution:  -> not a bug
stage:  -> resolved
status: open -> closed

___
Python tracker 
<https://bugs.python.org/issue47198>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47170] py launcher on windows opens new terminal window when parsing python script with shebang

2022-04-01 Thread Eryk Sun


Eryk Sun  added the comment:

> This is Windows (shell) behaviour. To avoid this, you need to 
> add the .py extension to the PATHEXT environment variable.

PowerShell reuses the current console session only if .PY is set in PATHEXT. 
Otherwise Python gets executed with a flag that tells the system to allocate a 
new console session.

For CMD, PATHEXT only affects the file search, not how the file is executed. If 
the internal START command isn't used, CMD reuses the current console session. 
The START command defaults to making the system allocate a new console, unless 
the /B option is used.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47170>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47161] pathlib method relative_to doesnt work with // in paths

2022-04-01 Thread Eryk Sun


Eryk Sun  added the comment:

> Hmm..., I get it, but Im not gonna lie it's pretty confusing given 
> that in other places `//` works as a substitute for `/`. Maybe it 
> should be mentioned in the documentation?

In Linux, the system resolves "//" as just "/". In other POSIX systems, such as 
Cygwin or MSYS2 (running in Windows), "//" is a UNC path of the form 
"//server/share/filepath". I would expect resolve() to handle this. For example:

Linux:

>>> p = pathlib.Path('//tmp')
>>> p
PosixPath('//tmp')
>>> p.resolve()
PosixPath('/tmp')

However, resolve() is broken for a UNC path in 3.9 under MSYS2:

>>> p = pathlib.Path('//localhost/C$/temp')
>>> p.exists()
True
>>> p.resolve()
PosixPath('/localhost/C$/temp')
>>> p.resolve().exists()
False

realpath() is also broken for this case in 3.9 under MSYS2:

>>> os.path.realpath(p)
'/localhost/C$/temp'

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47161>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue39090] Document various options for getting the absolute path from pathlib.Path objects

2022-04-01 Thread Eryk Sun


Eryk Sun  added the comment:

> Now a file that doesn't exist:
> >>> mike = Path("palin.jpg")
> >>> mike.resolve()
> WindowsPath('palin.jpg')

This is a bug in resolve(). It was fixed in 3.10+ by switching to 
ntpath.realpath(). I don't remember why a fix for 3.9 was never applied. Work 
on the PR may have stalled due to a minor disagreement.

> 'C:\Windows\..\Program Files' and '/usr/../bin' == relative path

No, a relative path depends on either the current working directory or, for a 
symlink target, the path of the directory that contains the symlink.

In Windows, a rooted path such as r"\spam" is a relative path because it 
depends on the drive of the current working directory. For example, if the 
current working directory is r"Z:\eggs", then r"\spam" resolves to r"Z:\spam". 
Also, a drive-relative paths such as "Z:spam" depends on the working directory 
of the given drive. Windows supports a separate working directory for each 
drive. For example, if the working directory of drive "Z:" is r"Z:\eggs", then 
"Z:spam" resolves to r"Z:\eggs\spam".

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue39090>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47134] Document the meaning of the number in OverflowError

2022-03-26 Thread Eryk Sun


Eryk Sun  added the comment:

The error code for `1e+300 ** 2` is ERANGE, from calling libm pow(). Since 
pow() returns a double, there's no way to indicate an error in the return 
value. Instead, C errno is set to 0 beforehand and checked for a non-zero error 
value after the call. If the error code isn't ERANGE, then float.__pow__() 
raises ValueError instead of OverflowError. Here's the relevant snippet from 
float_pow() in Objects/floatobject.c:

/* Now iv and iw are finite, iw is nonzero, and iv is
 * positive and not equal to 1.0.  We finally allow
 * the platform pow to step in and do the rest.
 */
errno = 0;
ix = pow(iv, iw);
_Py_ADJUST_ERANGE1(ix);
if (negate_result)
ix = -ix;

if (errno != 0) {
/* We don't expect any errno value other than ERANGE, but
 * the range of libm bugs appears unbounded.
 */
PyErr_SetFromErrno(errno == ERANGE ? PyExc_OverflowError :
 PyExc_ValueError);
return NULL;
}
return PyFloat_FromDouble(ix);

Here's a direct example using ctypes:

import ctypes
import errno

libm = ctypes.CDLL('libm.so.6', use_errno=True)
libm.pow.argtypes = (ctypes.c_double, ctypes.c_double)
libm.pow.restype = ctypes.c_double

>>> errno.ERANGE
34
>>> ctypes.set_errno(0)
0
>>> libm.pow(1e300, 2)
inf
>>> ctypes.get_errno() == errno.ERANGE
True

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47134>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue32642] add support for path-like objects in sys.path

2022-03-26 Thread Eryk Sun


Eryk Sun  added the comment:

> I've got in mind a PyListObject subclass with calls to PyOS_FSPath 
> before insert, append, extend and __getitem__.

The sys module doesn't prevent rebinding sys.path. Most code I think is careful 
to update sys.path. But surely some code replaces it with a new list instead of 
updating via insert(), append(), extend(), or updating a slice such as 
`sys.path[:] = new_path`. For example, ModifiedInterpreter.transfer_path() in 
Lib/idlelib/pyshell.py rebinds sys.path. It doesn't have to, but it does.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue32642>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47086] Include HTML docs with Windows installer instead of CHM

2022-03-22 Thread Eryk Sun


Change by Eryk Sun :


--
nosy:  -eryksun

___
Python tracker 
<https://bugs.python.org/issue47086>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47086] Include HTML docs with Windows installer instead of CHM

2022-03-22 Thread Eryk Sun


Eryk Sun  added the comment:

Do you have any thoughts about distributing the docs in ePub format?

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47086>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46788] regrtest fails to start on missing performance counter names

2022-03-22 Thread Eryk Sun


Change by Eryk Sun :


Removed file: https://bugs.python.org/file50695/loadtracker.py

___
Python tracker 
<https://bugs.python.org/issue46788>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46788] regrtest fails to start on missing performance counter names

2022-03-22 Thread Eryk Sun


Change by Eryk Sun :


--
Removed message: https://bugs.python.org/msg415781

___
Python tracker 
<https://bugs.python.org/issue46788>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47096] Use the PDH API in WindowsLoadTracker

2022-03-22 Thread Eryk Sun


New submission from Eryk Sun :

In bpo-44336, a new version of WindowsLoadTracker was implemented in 
Lib/test/libregrtest/win_utils.py. This version resolves issues with the 
previous implementation that spawned typeperf.exe. The new implementation uses 
the registry API's HKEY_PERFORMANCE_DATA pseudohandle to access the performance 
counters. This requires hard-coding "2" as the key name of the system object, 
44 as the index of the "Processor Queue Length" counter, and also several 
struct definitions. 

The HKEY_PERFORMANCE_DATA 'key' just wraps the API as an alternate way to 
consume counter data. Instead of taking this detour through a minimalist 
interface just to use the API in a roundabout way, it would be better to 
implement a few of the Performance Data Helper functions in _winapi. I've 
implemented a prototype in ctypes to demonstrate this, which I'm attaching as 
"loadtracker.py". The functions that would need to be implemented are 
PdhOpenQueryW(), PdhAddEnglishCounterW(), PdhCollectQueryData(), 
PdhGetRawCounterValue(), and PdhCloseQuery().

--
components: Windows
files: loadtracker.py
messages: 415808
nosy: eryksun, jkloth, paul.moore, steve.dower, tim.golden, zach.ware
priority: normal
severity: normal
status: open
title: Use the PDH API in WindowsLoadTracker
type: enhancement
Added file: https://bugs.python.org/file50696/loadtracker.py

___
Python tracker 
<https://bugs.python.org/issue47096>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47093] Documentation Fix: Remove .bat when activating venv on windows

2022-03-22 Thread Eryk Sun


Eryk Sun  added the comment:

Running `tutorial-env\Scripts\activate` should suffice. The .bat script is for 
CMD, and the .ps1 script is for PowerShell. The shell should run the right 
script without having to include the extension. 

In Windows 10+, if you use a case-sensitive directory for the virtual 
environment, note that the script name for PowerShell is "Activate.ps1". 
PowerShell 7+ checks the directory for any name that case-insensitively matches 
"activate", but you'll have to run `tutorial-env\Scripts\Activate` in 
PowerShell 5.1.

--
components: +Windows
keywords: +easy
nosy: +eryksun, paul.moore, steve.dower, tim.golden, zach.ware
stage:  -> needs patch
versions: +Python 3.10, Python 3.9

___
Python tracker 
<https://bugs.python.org/issue47093>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46788] regrtest fails to start on missing performance counter names

2022-03-22 Thread Eryk Sun


Eryk Sun  added the comment:

I implemented a ctypes prototype that replaces the registry-based 
implementation with the API calls PdhOpenQueryW(), PdhAddEnglishCounterW(), 
PdhCollectQueryData(), PdhGetRawCounterValue(), and PdhCloseQuery(). I'm 
attaching the script, but here's the class itself without the ctypes 
definitions:

class WindowsLoadTracker():
"""
This class asynchronously reads the system "Processor Queue Length"
counter to calculate the system load on Windows.  A raw thread is
used to avoid interfering with tests of the threading module.
"""

def __init__(self):
self._values = []
self._load = None
self._hrunning = kernel32.CreateEventW(None, True, False, None)
self._hstopped = kernel32.CreateEventW(None, True, False, None)
self._hquery = wintypes.HANDLE()
self._hcounter = wintypes.HANDLE()
pdh.PdhOpenQueryW(None, None, ctypes.byref(self._hquery))
pdh.PdhAddEnglishCounterW(self._hquery,
  r"\System\Processor Queue Length",
  None,
  ctypes.byref(self._hcounter))
pdh.PdhCollectQueryData(self._hquery)
_thread.start_new_thread(self._update_load, (), {})

def _update_load(self,
 # Localize module access to prevent shutdown errors.
 WaitForSingleObject=_winapi.WaitForSingleObject,
 SetEvent=kernel32.SetEvent):
# run until signaled to stop
while WaitForSingleObject(self._hrunning, 1000):
self._calculate_load()
# notify stopped
SetEvent(self._hstopped)

def _calculate_load(self,
# Lcalize module access to prevent shutdown errors.
PdhCollectQueryData=pdh.PdhCollectQueryData,
PdhGetRawCounterValue=pdh.PdhGetRawCounterValue):
counter_type = wintypes.DWORD()
raw = PDH_RAW_COUNTER()
PdhCollectQueryData(self._hquery)
PdhGetRawCounterValue(self._hcounter,
  ctypes.byref(counter_type),
  ctypes.byref(raw))
if raw.CStatus < 0:
return
processor_queue_length = raw.FirstValue

# Use an exponentially weighted moving average, imitating the
# load calculation on Unix systems.
# 
https://en.wikipedia.org/wiki/Load_(computing)#Unix-style_load_calculation
# 
https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
if self._load is not None:
self._load = (self._load * LOAD_FACTOR_1
+ processor_queue_length  * (1.0 - 
LOAD_FACTOR_1))
elif len(self._values) < NVALUE:
self._values.append(processor_queue_length)
else:
self._load = sum(self._values) / len(self._values)

def getloadavg(self):
return self._load

def close(self,
  # Localize module access to prevent shutdown errors.
  WaitForSingleObject=_winapi.WaitForSingleObject,
  INFINITE=_winapi.INFINITE,
  SetEvent=kernel32.SetEvent,
  CloseHandle=_winapi.CloseHandle,
  PdhCloseQuery=pdh.PdhCloseQuery):
if self._hrunning is None:
return
# Tell the update thread to quit.
SetEvent(self._hrunning)
# Wait for the update thread to stop before cleanup.
WaitForSingleObject(self._hstopped, INFINITE)
CloseHandle(self._hrunning)
CloseHandle(self._hstopped)
PdhCloseQuery(self._hquery)
self._hrunning = self._hstopped = None

def __del__(self):
self.close()
return

--
Added file: https://bugs.python.org/file50695/loadtracker.py

___
Python tracker 
<https://bugs.python.org/issue46788>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46788] regrtest fails to start on missing performance counter names

2022-03-21 Thread Eryk Sun


Eryk Sun  added the comment:

I was just wondering whether it's worth implementing it using the API. To be 
clear, I wasn't implying to hold off on applying Jeremy's PR. The existing code 
is causing him problems, and he has a working solution.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46788>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue43702] [Windows] correctly sort and remove duplicates in _winapi getenvironment()

2022-03-19 Thread Eryk Sun


Eryk Sun  added the comment:

> which name should be stored if they are duplicated with case insensitive?

Ideally os.environ would preserve the original case of the process environment, 
and os.environ.copy() would return a copy that's also case insensitive. That 
would prevent most problems with duplicates keys. See msg387676 in bpo-28824, 
and msg414319 in bpo-15373.

In msg390038 I suggested keeping the first key that's encountered. However, 
dicts preserve insertion order nowadays, so one could assume that the last one 
is the one that the caller wants to keep.

--

___
Python tracker 
<https://bugs.python.org/issue43702>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47037] Build problems on Windows

2022-03-18 Thread Eryk Sun


Eryk Sun  added the comment:

The main entry point for python[_d].exe should support a command-line -X option 
or environment variable that suppresses Windows error/assert/warn reporting, or 
redirects it to stderr in verbose mode. This would be useful to simplify 
everyone's automated testing. It should require opting in, both for backward 
compatibility and also because the dialogs are useful for interactive testing.

--

___
Python tracker 
<https://bugs.python.org/issue47037>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47037] Build problems on Windows

2022-03-17 Thread Eryk Sun


Change by Eryk Sun :


--
priority: normal -> critical

___
Python tracker 
<https://bugs.python.org/issue47037>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47037] Build problems on Windows

2022-03-16 Thread Eryk Sun


Eryk Sun  added the comment:

> Ouch, is Python crashes because of an unsupported strftime call?

It's not a crash. It's a CRT error report dialog, which is enabled by default 
for the _CRT_ASSERT and _CRT_ERROR macros in debug builds. This dialog can be 
helpful when debugging interactively. It gives a developer the option to abort, 
retry, or ignore. If a debugger is attached, the retry option breaks into the 
debugger. I attached a debugger to the test runner to diagnose the problem with 
format code "%4Y". For example:

>>> time.strftime('%4Y')
Debug Assertion Failed!
[...]
(Press Retry to debug the application)
(1a6c.101c): Break instruction exception - code 8003 (first chance)
ucrtbased!_Wcsftime_l+0x5af:
7ff8`a582b9af cc  int 3

backtrace:

0:000> kc 4
Call Site
ucrtbased!_Wcsftime_l
ucrtbased!_Strftime_l
ucrtbased!strftime
python311_d!time_strftime

locals:

0:000> dv
  _Expr_val = 0n0
 string = 0x0226`fe38d1c0 ""
   max_size = 0x400
 format = 0x0226`fe37e7d0 "%4Y"
timeptr = 0x0041`c1feeda0
lc_time_arg = 0x`
 locale = 0x`
  locale_update = class _LocaleUpdate
  format_it = 0x0226`fe37e7d2 "4Y"
  remaining = 0x400
lc_time = 0x7ff8`a5868550
 failed = true
  string_it = 0x0226`fe38d1c0 ""

resume, with the STATUS_BREAKPOINT (0x8003) exception handled:

0:000> gh
Traceback (most recent call last):
  File "", line 1, in 
ValueError: Invalid format string
>>> 


For non-interactive testing the report mode needs to be configured to 0 (no 
report) or to report to stderr. For example:

>>> msvcrt.CrtSetReportMode(msvcrt.CRT_ERROR, msvcrt.CRTDBG_MODE_FILE)
4
>>> msvcrt.CrtSetReportFile(msvcrt.CRT_ERROR, msvcrt.CRTDBG_FILE_STDERR)
18446744073709551615
>>> os.abort()
abort() has been called

For time.strftime('%4Y'), the source of the report is _VALIDATE_RETURN(false, 
EINVAL, 0) in _Wcsftime_l(). This macro includes an _ASSERT_EXPR(expr, msg) 
check. In a debug build, this calls _CrtDbgReportW(_CRT_ASSERT, ...) if the 
expression is false. If the latter returns 1 (retry), the __debugbreak() 
intrinsic is called to break into the debugger. To suppress the dialog, either 
temporarily set the CRT_ASSERT report mode to 0, or set it to report to stderr. 
For example:

>>> msvcrt.CrtSetReportMode(msvcrt.CRT_ASSERT, msvcrt.CRTDBG_MODE_FILE)
4
>>> msvcrt.CrtSetReportFile(msvcrt.CRT_ASSERT, msvcrt.CRTDBG_FILE_STDERR)
18446744073709551615

>>> time.strftime('%4Y')
minkernel\crts\ucrt\src\appcrt\time\wcsftime.cpp(1163) : Assertion failed: 
false
Traceback (most recent call last):
  File "", line 1, in 
ValueError: Invalid format string

--

___
Python tracker 
<https://bugs.python.org/issue47037>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47037] Build problems on Windows

2022-03-16 Thread Eryk Sun


Eryk Sun  added the comment:

bpo-46587 added top-level code to "Lib/test/support/__init__.py" to check 
whether time.strftime("%4Y") works. Since "Lib/test/libregrtest/setup.py" 
imports the support module, that code executes before suppress_msvcrt_asserts() 
is called by setup_tests().

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47037>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46890] getpath problems with framework build

2022-03-16 Thread Eryk Sun


Eryk Sun  added the comment:

> This means that "python -S" doesn't work with a virtual environment. 

Does that matter? I've only used a virtual environment to override or extend 
the system site packages. Is there a reason to use one without site packages?

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46890>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47025] bytes do not work on sys.path

2022-03-15 Thread Eryk Sun


Eryk Sun  added the comment:

> this is a regression from 3.2

In Windows, bytes paths in sys.path do not work in 3.2+. I didn't test 3.0 and 
3.1, but practically one can say that bytes paths were never supported in 
Python 3 on Windows.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47025>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46950] Windows 11, VENV not working with case sensitive windows paths

2022-03-14 Thread Eryk Sun


Eryk Sun  added the comment:

In 3.10, you should be able to work around the problem for the venv 
site-packages directory by setting the environment variable "PYTHONPLATLIBDIR" 
to "Lib". This sets sys.platlibdir, which the site module uses to create the 
site-packages directory. The default value is "lib", which isn't properly 
capitalized.

In 3.11, sys.platlibdir defaults to "DLLs" in Windows, and the site module 
ignores this attribute. However, the site module does hard code the properly 
capitalized value "Lib".

In POSIX, sys.platlibdir (e.g. "lib" or "lib64") is a common base directory for 
the standard library and its extension modules. It isn't just the directory for 
extension modules, as might be implied by changing the value to "DLLs" in 
Windows. The "DLLs" directory in Windows Python is split off from "Lib" (for 
some legacy reason, I suppose), so Windows would need separate sys.platlibdir 
("Lib") and sys.platextdir ("DLLs") values, if that mattered, which it doesn't 
since the names are fixed.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46950>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46785] On Windows, os.stat() can fail if called while another process is creating or deleting the file

2022-03-14 Thread Eryk Sun


Eryk Sun  added the comment:

I was following the pattern of StatAttributeTests.test_access_denied(), which 
uses the current user's temp directory to get a filesystem that supports 
security. 

It would probably be better to skip tests if the filesystem of the current 
working directory doesn't support the test, e.g. if it needs NTFS or needs 
support for security, hard links, or reparse points. For example, 
Win32JunctionTests should be skipped if reparse points aren't supported. The 
os_helper module could implement a function that calls GetVolumeInformationW() 
to get the filesystem name (e.g. "Ntfs") and flags (e.g. FILE_PERSISTENT_ACLS, 
FILE_SUPPORTS_HARD_LINKS, FILE_SUPPORTS_REPARSE_POINTS).

--

___
Python tracker 
<https://bugs.python.org/issue46785>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46785] On Windows, os.stat() can fail if called while another process is creating or deleting the file

2022-03-13 Thread Eryk Sun


Eryk Sun  added the comment:

> Why catch ERROR_NOT_READY and ERROR_BAD_NET_NAME as well?

When os.stat() falls back on FindFirstFileW(), an error that means the file 
doesn't exist should be kept. ERROR_BAD_NET_NAME is an obvious error to keep 
because it's already mapped to ENOENT (i.e. file not found). This error 
typically means that share was removed or the network went down. 
ERROR_NOT_READY typically means that the media was removed from a volume (e.g. 
ejected disk). pathlib.Path.exists() returns False for ERROR_NOT_READY.

> Why can't the filename of the "foo"-like file in the test be 
> simply os_helper.TESTFN, as done in some other tests?

I suppose the current working directory will be fine. I was looking to keep the 
test on a NTFS filesystem, with known behavior, but there's no hard guarantee 
that the user's temp directory is on the system volume.

> I noticed some code snippets used in tests are wrapped in a 
> seemingly-redundant `if 1: ...` statement. Why is that?

The `if 1` test is logically redundant but convenient for using a multiline 
string that has an arbitrary indentation level. For example:

>>> exec('''
... print(42)
... ''')
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 2
print(42)
IndentationError: unexpected indent

>>> exec('''if 1:
... print(42)
... ''')
42

--

___
Python tracker 
<https://bugs.python.org/issue46785>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue47001] deadlock in ctypes?

2022-03-13 Thread Eryk Sun


Eryk Sun  added the comment:

Pointers to resource type/name strings use the lower 16-bit range for integer 
identifiers such as RT_ICON (3) and RT_GROUP_ICON (14). C code checks for these 
cases using the IS_INTRESOURCE() macro.

It's incorrect to use a simple C string pointer type such as ctypes.c_wchar_p 
(i.e. wintypes.LPWSTR) for a resource type/name string. Simple types get 
automatically converted to the corresponding Python type (e.g. str), but a 
16-bit ID is not a valid pointer. Support for resource IDs can be implemented 
by a subclass of ctypes.c_wchar_p, since subclasses of simple types don't get 
converted automatically. For example:

class LPWSTR(ctypes.c_wchar_p):
@property
def value(self):
v = ctypes.c_void_p.from_buffer(self).value or 0
if v >> 16 == 0:
return v
return super().value

@value.setter
def value(self, v):
ctypes.c_wchar_p.value.__set__(self, v)

def __eq__(self, other):
if not isinstance(other, __class__):
return NotImplemented
return self.value == other.value

def __hash__(self):
return hash(self.value)


RT_ICON = LPWSTR(3)
RT_GROUP_ICON = LPWSTR(RT_ICON.value + 11)

> kernel32 = ctypes.windll.kernel32

I recommend using `kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)`, 
which creates function pointers that capture the last error value before they 
return. Call ctypes.get_last_error() to get the captured error value. To get an 
exception for the error value, use ctypes.WinError(ctypes.get_last_error()). 
This is more reliable since there's a chance that the thread executes some 
WinAPI code that modifies the last error before GetLastError() is called. This 
is particularly likely when working with ctypes calls in the interactive shell.

Using a separate WinDLL() instance also isolates a library from other 
libraries. Since ctypes.windll caches modules, which cache function pointers, 
it's a problem when multiple libraries require different function prototypes 
(i.e. restype, argtypes, errcheck). The last library to set the prototype wins. 
The other libraries will probably be broken in some way. Their function calls 
may raise a ctypes.ArgumentError, or their post-call errcheck() isn't used, or 
their required restype isn't returned.

> hmod = GetModuleHandle(sys.executable)

This is unnecessary and possibly wrong since there's no guarantee that 
sys.executable is a valid file, let alone a file that's loaded as a module in 
the current process. Pass hModule as NULL (None) to use the main module of the 
process (i.e. the process image / executable).

>  context structure that is used to use regular python functions as callbacks
>  where external C code expects C callbacks with a certain signature.

Generally there's no need to use C context parameters, which aren't necessary 
in object-oriented and functional programming paradigms. The callback function 
can defined as a nested function, with access to closure variables, or as a 
method, with access to instance attributes.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue47001>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46988] if a python program is execute by subprocess, the python program can't output unicode characters and raise UnicodeEncodeError

2022-03-11 Thread Eryk Sun


Change by Eryk Sun :


--
superseder:  -> Encoding error running in subprocess with captured output

___
Python tracker 
<https://bugs.python.org/issue46988>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46785] On Windows, os.stat() can fail if called while another process is creating or deleting the file

2022-03-11 Thread Eryk Sun


Eryk Sun  added the comment:

Itai, you can add a test to Win32NtTests in Lib/test/test_os.py. Maybe spawn a 
child process that creates and unlinks a file in a loop. In the parent process 
execute a loop that tries to stat the file and ignores errors when the file or 
path isn't found. For example:

@support.requires_subprocess()
def test_stat_unlink_race(self):
# bpo-46785: the implementation of os.stat() falls back on reading
# the parent directory if CreateFileW() fails with a permission
# error. If reading the parent directory fails because the file or
# directory is subsequently unlinked or because the volume or
# share is no longer available, then the original permission error
# should not be restored.
fname = os.path.join(os.environ['TEMP'], os_helper.TESTFN + '_46785')
self.addCleanup(os_helper.unlink, fname)
command = '''if 1:
import os
import sys
fname = sys.argv[1]
while True:
try:
with open(fname, "w") as f:
pass
except OSError:
pass
try:
os.remove(fname)
except OSError:
pass
'''
ignored_errors = (
2,  # ERROR_FILE_NOT_FOUND
3,  # ERROR_PATH_NOT_FOUND
21, # ERROR_NOT_READY
67, # ERROR_BAD_NET_NAME
)
deadline = time.time() + 5
p = subprocess.Popen([sys.executable, '-c', command, fname])
try:
while time.time() < deadline:
try:
os.stat(fname)
except OSError as e:
if e.winerror not in ignored_errors:
raise
finally:
p.terminate()

As the above test shows, I think the error should also be kept if 
attributes_from_dir() fails with ERROR_NOT_READY or ERROR_BAD_NET_NAME. For 
example:

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, , )) {
/* Cannot read the parent directory. */
switch (GetLastError()) {
// keep these error codes
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_NOT_READY:
case ERROR_BAD_NET_NAME:
break;
// restore the error from CreateFileW()
default:
SetLastError(error);
}
return -1;
}

--

___
Python tracker 
<https://bugs.python.org/issue46785>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46966] c_void_p array is a footgun on I32LP64 systems

2022-03-09 Thread Eryk Sun


Eryk Sun  added the comment:

> I'm not sure what can be done here (maybe a truncation warning?)

For a function pointer, the default argument conversion for Python integers is 
the platform int type. Ideally, Python integer arguments would be converted to 
a type that matches the platform word size, as is the default behavior for 
integer arguments in C. But the behavior can't be changed at this point. 
Ideally, it would behave the same in LP64 (Unix) and LLP64 (Windows) systems, 
but OverflowError is raised in LLP64 because ctypes first converts to a long 
int. OverflowError could be manually raised if `(unsigned long)value > 
UINT_MAX`, but I think it's also too late to make that change. Scripts have 
worked around the current behavior for about two decades. Raising a warning is 
really the best that could be done, if anything is done at all.

The best solution is to not use bare function pointers without setting the 
prototype. If a function pointer is created as an attribute of a CDLL instance, 
the common way to define the prototype is by setting the function's `argtypes` 
and `restype` attributes.

Another ctypes concept to be aware of is that subclasses of simple types do not 
get converted by default when accessed as C fields, array subscripts, or 
function results. For example:

class my_void_p(ctypes.c_void_p):
pass

>>> a = (my_void_p * 1)()
>>> isinstance(a[0], my_void_p)
True

--
nosy: +eryksun
versions:  -Python 3.7, Python 3.8

___
Python tracker 
<https://bugs.python.org/issue46966>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue37609] support "UNC" device paths in ntpath.splitdrive

2022-03-06 Thread Eryk Sun


Change by Eryk Sun :


--
Removed message: https://bugs.python.org/msg390391

___
Python tracker 
<https://bugs.python.org/issue37609>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46916] There is a problem with escape characters in the path passed in by pathlib.Path.mkdir()

2022-03-04 Thread Eryk Sun


Eryk Sun  added the comment:

> I thought pathlib would solve this problem completely now, 
> without having to replace the slashes.

pathlib has nothing to do with how the Python language compiles string 
literals. A string *literal* is Python source code that gets compiled and 
instantiated as a string object. 

There is no technical problem with using r'F:\ceven\test2'. Using a raw string 
literal disables backslash escapes. But many developers find it inconvenient to 
have to use raw strings or to have to escape backslashes by doubling them. Plus 
raw string literals can't represent a path that ends with a backslash, such as 
C:\. 

The Windows API supports forward slash as a path separator in many cases, but 
not always, and also not always for paths passed on the command line. So it's 
preferable to always normalize paths. pathlib takes care of this for you. You 
can initialize conveniently with forward slashes, but get a Windows path string 
that uses backslashes.

--

___
Python tracker 
<https://bugs.python.org/issue46916>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46916] There is a problem with escape characters in the path passed in by pathlib.Path.mkdir()

2022-03-03 Thread Eryk Sun


Eryk Sun  added the comment:

> e = Path(r'F:\ceven\test2')

Using forward slashes in the string literal is preferred, e.g. 
Path('F:/ceven/test2'). This avoids the problem of backslash escapes in string 
literals. The Path constructor parses the path and stores it internally as 
component parts. When the path object is needed as a string, os.fspath() 
returns the path using the platform's preferred path separator. A WindowsPath 
or PureWindowsPath uses backslash as the path separator. For example:

>>> os.fspath(pathlib.PureWindowsPath('F:/ceven/test2'))
'F:\\ceven\\test2'

--

___
Python tracker 
<https://bugs.python.org/issue46916>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46916] There is a problem with escape characters in the path passed in by pathlib.Path.mkdir()

2022-03-03 Thread Eryk Sun


Eryk Sun  added the comment:

> FileNotFoundError: [WinError 3] The system cannot find 
> the path specified: 'F:\\ceven\\test2'

The Windows error code, ERROR_PATH_NOT_FOUND (3), indicates that the parent 
path, r"F:\ceven", does not exist. Try e.mkdir(parents=True) [1].

[1] https://docs.python.org/3/library/pathlib.html#pathlib.Path.mkdir

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46916>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46905] winsound.PlaySound should accept pathlib.Path instances

2022-03-02 Thread Eryk Sun


Change by Eryk Sun :


--
components: +Windows
keywords: +easy (C)
nosy: +paul.moore, steve.dower, tim.golden, zach.ware

___
Python tracker 
<https://bugs.python.org/issue46905>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46905] winsound.PlaySound should accept pathlib.Path instances

2022-03-02 Thread Eryk Sun


Eryk Sun  added the comment:

PlaySound() is implemented by the C function winsound_PlaySound_impl() in 
"PC/winsound.c". To support pathlike objects, it could use `soundname = 
PyOS_FSPath(sound)`, and require soundname to be a Unicode string via 
PyUnicode_Check(soundname). Or it could more generically support any buffer or 
pathlike object via PyUnicode_FSDecoder(sound, ). This call decodes 
byte strings using the filesystem encoding, which is UTF-8 unless legacy ANSI 
mode is enabled.

--
nosy: +eryksun
stage:  -> needs patch
versions: +Python 3.11

___
Python tracker 
<https://bugs.python.org/issue46905>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46888] SharedMemory.close() destroys memory

2022-03-02 Thread Eryk Sun


Eryk Sun  added the comment:

Putting words into action, here's an example of what a privileged process (e.g. 
running as SYSTEM) can do if a script or application is written to call the 
undocumented NT API function NtMakePermanentObject(). A use case would be a 
script running as a system service that needs a shared-memory section object to 
persist after the service is stopped or terminated, e.g. such that the section 
is still accessible if the service is resumed. Anyway, this is just an example 
of what's possible, from a technical perspective. Also, note that I granted 
SeCreatePermanentPrivilege to my current user for this example, so it's 
certainly not required to use the SYSTEM account. An administrator can grant 
this privilege to any account.

By default, named kernel objects are temporary:

>>> import os, _winapi, ctypes
>>> from multiprocessing.shared_memory import SharedMemory
>>> name = f'spam_{os.getpid()}'
>>> m = SharedMemory(name, True, 8192)
>>> m.close()
>>> try: SharedMemory(name)
... except OSError as e: print(e)
...
[WinError 2] The system cannot find the file specified: 'spam_6592'

Global permanent example:

Enable privileges for the current thread:

>>> import win32api; from win32security import *
>>> ImpersonateSelf(SecurityImpersonation)
>>> da = TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES
>>> ht = OpenThreadToken(win32api.GetCurrentThread(), da, False)
>>> ps = [[0, SE_PRIVILEGE_ENABLED], [0, SE_PRIVILEGE_ENABLED]]
>>> ps[0][0] = LookupPrivilegeValue(None, 'SeCreatePermanentPrivilege')
>>> ps[1][0] = LookupPrivilegeValue(None, 'SeCreateGlobalPrivilege')
>>> AdjustTokenPrivileges(ht, False, ps)
((16, 0),)

Create a global section object, and make it permanent:

>>> name = rf'Global\spam_{os.getpid()}'
>>> m = SharedMemory(name, True, 8192)
>>> from win32con import DELETE
>>> h = _winapi.OpenFileMapping(DELETE, False, name)
>>> ntdll = ctypes.WinDLL('ntdll')
>>> ntdll.NtMakePermanentObject(h)
0
>>> _winapi.CloseHandle(h)

A permanent object persists after the last handle is closed:

>>> m.close()
>>> m = SharedMemory(name) # This works now.

Make the section object temporary again:

>>> h = _winapi.OpenFileMapping(DELETE, False, name)
>>> ntdll.NtMakeTemporaryObject(h)
0
>>> _winapi.CloseHandle(h)
>>> m.close()
>>> try: SharedMemory(name)
... except OSError as e: print(e)
...
[WinError 2] The system cannot find the file specified: 'Global\\spam_6592'

--

___
Python tracker 
<https://bugs.python.org/issue46888>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue28824] os.environ should preserve the case of the OS keys ?

2022-03-02 Thread Eryk Sun


Change by Eryk Sun :


--
Removed message: https://bugs.python.org/msg414332

___
Python tracker 
<https://bugs.python.org/issue28824>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue28824] os.environ should preserve the case of the OS keys ?

2022-03-02 Thread Eryk Sun


Eryk Sun  added the comment:

Putting words into action, here's an example of what a privileged process (e.g. 
running as SYSTEM) can do if a script or application is written to call the 
undocumented NT API function NtMakePermanentObject(). A use case would be a 
script running as a system service that needs a shared-memory section object to 
persist after the service is stopped or terminated, e.g. such that the section 
is still accessible if the service is resumed. Anyway, this is just an example 
of what's possible, from a technical perspective.

By default, named kernel objects are temporary:

>>> import os, _winapi, ctypes
>>> from multiprocessing.shared_memory import SharedMemory
>>> name = f'spam_{os.getpid()}'
>>> m = SharedMemory(name, True, 8192)
>>> m.close()
>>> try: SharedMemory(name)
... except OSError as e: print(e)
...
[WinError 2] The system cannot find the file specified: 'spam_6592'

Global permanent example:

Enable privileges for the current thread:

>>> import win32api; from win32security import *
>>> ImpersonateSelf(SecurityImpersonation)
>>> da = TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES
>>> ht = OpenThreadToken(win32api.GetCurrentThread(), da, False)
>>> ps = [[0, SE_PRIVILEGE_ENABLED], [0, SE_PRIVILEGE_ENABLED]]
>>> ps[0][0] = LookupPrivilegeValue(None, 'SeCreatePermanentPrivilege')
>>> ps[1][0] = LookupPrivilegeValue(None, 'SeCreateGlobalPrivilege')
>>> AdjustTokenPrivileges(ht, False, ps)
((16, 0),)

Create a global section object, and make it permanent:

>>> name = rf'Global\spam_{os.getpid()}'
>>> m = SharedMemory(name, True, 8192)
>>> from win32con import DELETE
>>> h = _winapi.OpenFileMapping(DELETE, False, name)
>>> ntdll = ctypes.WinDLL('ntdll')
>>> ntdll.NtMakePermanentObject(h)
0
>>> _winapi.CloseHandle(h)

A permanent object persists after the last handle is closed:

>>> m.close()
>>> m = SharedMemory(name) # This works now.

Make the section object temporary again:

>>> h = _winapi.OpenFileMapping(DELETE, False, name)
>>> ntdll.NtMakeTemporaryObject(h)
0
>>> _winapi.CloseHandle(h)
>>> m.close()
>>> try: SharedMemory(name)
... except OSError as e: print(e)
...
[WinError 2] The system cannot find the file specified: 'Global\\spam_6592'

--

___
Python tracker 
<https://bugs.python.org/issue28824>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46888] SharedMemory.close() destroys memory

2022-03-01 Thread Eryk Sun


Eryk Sun  added the comment:

> The persistent mode sounds just like Python shared memory also works
> on Linux (where I can have these /dev/shm/* files even after the 
> Python process ends) but I think on Windows, Python is not using 
> the persistent mode and thus the shared memory goes away, in contrast
> to how it works on Linux.

Unix has the notion that everything is a file as a central organizing concept. 
As such, Linux opts to implement POSIX shm_open() [1] with a tmpfs [2] 
filesystem that's mounted on /dev/shm. tmpfs filesystems exist in virtual 
memory. They're not persistent in the sense of being created on a physical disk 
that provides persistent storage.

Windows has the notion that everything is an object as a central organizing 
concept. Internally, the system has a "\" root object directory, which contains 
other object directories and object symbolic links (unrelated to filesystem 
symlinks). Each object directory, including the root directory, contains named 
kernel objects. 

Named device objects are normally created in r"\Device", such as 
r"\Device\HarddiskVolume2", and global symbolic links to devices are created in 
r"\GLOBAL??", such as r"\GLOBAL??\C:" -> r"\Device\HarddiskVolume2" for the 
"C:" drive. The root registry key object is r"\REGISTRY", which contains 
dynamic key objects such as r"\REGISTRY\MACHINE" (referenced via the 
pseudohandle HKEY_LOCAL_MACHINE) and "\REGISTRY\USER" (referenced via the 
pseudohandle HKEY_USERS), which in turn contain other keys such as 
"\REGISTRY\MACHINE\SOFTWARE" on which registry hives are mounted.

For naming global kernel objects and session 0 objects, the Windows API 
internally uses the directory r"\BaseNamedObjects". For interactive Windows 
sessions, it uses r"\Sessions\\BaseNamedObjects" and, for app 
containers, subdirectories of r"\Sessions\\AppContainerNamedObjects". To explicitly name an object in the global 
directory, use the path r"Global\". The "Global" prefix is 
implemented as an object symbolic link to r"\BaseNamedObjects". Of course, 
that's an internal implementation detail; the API just refers to the 
r"Global\\" prefix.

Naming a kernel object in r"\BaseNamedObjects" is nearly equivalent to creating 
a file in a tmpfs filesystem in Linux, with one major difference. Objects are 
reference counted, with a handle reference count and a pointer reference count. 
Opening a handle increments both counters, but kernel code can use just a 
pointer reference instead of opening a handle. By default, objects are 
temporary. As such, when their pointer reference count is decremented to 0, 
they get unlinked from the object namespace, if they're named objects, and 
deallocated.

It's possible to create a permanent object in the object namespace, either 
initially via the OBJ_PERMANENT attribute [3], or later on via 
NtMakePermanentObject(handle). However, making an object permanent is 
considered a super privilege in Windows, so privileged in fact that the Windows 
API doesn't support this capability, at least not as far as I know. By default, 
SeCreatePermanentPrivilege is only granted to the SYSTEM account. Also, 
creating a named section object (i.e. file mapping object) in a 
session-restricted directory such as r"\BaseNamedObjects" requires 
SeCreateGlobalPrivilege, which is only granted by default to administrators and 
system service accounts. 

Unix and Linux are less concerned about creating 'permanent' files and global 
shared memory in virtual filesystems such as tmpfs. The lifetime while the 
system is running is left up to the creator, which has to manually remove the 
file, such as via shm_unlink() [4]. Stale files could accumulate in "/dev/shm" 
when processes crash, which is why a separate resource tracker is required, 
implementing what the Windows kernel provides by default.

---
[1] https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html
[2] https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt
[3] 
https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes
[4] https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_unlink.html

--

___
Python tracker 
<https://bugs.python.org/issue46888>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue15373] copy.copy() does not properly copy os.environment

2022-03-01 Thread Eryk Sun


Eryk Sun  added the comment:

In bpo-28824, I suggested preserving the case of environment variables in 
Windows by using a case-insensitive subclass of str in the encodekey() 
function. This is self-contained by the use of the encodekey() and decodekey() 
functions in the mapping methods such as __iter__(). The reason for this is 
mostly about aesthetics, but also about faithfully displaying the actual 
environment variable names. Opinions vary, but to me "WindowsSdkVerBinPath" is 
both easier on my eyes and easier to read than "WINDOWSSDKVERBINPATH". The 
eyesore factor gets amplified when it's a wall of all upper-cased names 
'screaming' at me.

A copy via dict(os.environ) would use regular str keys and lose the 
case-insensitive, case-preserving property. It would be more useful if 
os.environ.copy() were implemented to return a copy that keeps the 
case-insensitive property for keys but disables updating the process 
environment. This could be implemented by making putenv and unsetenv parameters 
in the constructor. If self.putenv or self.unsetenv is None, then __setitem__() 
or __delitem__() woud have no effect on the process environment. The copy() 
method would return a new _Environ instance for self._data.copy(), one which of 
course disables updating the process environment.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue15373>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46888] SharedMemory.close() destroys memory

2022-03-01 Thread Eryk Sun


Eryk Sun  added the comment:

> Yes, named memory mappings only exist on Windows until the last 
> reference is closed, so this is a difference due to the underlying OS. 

That's true for the Windows API, so it's true for all practical purposes. In 
the underlying NT API, creating a permanent kernel object is possible by 
setting OBJ_PERMANENT in the initial object attributes [1], or subsequently via 
the undocumented system call NtMakePermanentObject(handle). Creating a 
permanent object requires SeCreatePermanentPrivilege, which normally is granted 
to just the SYSTEM account. An administrator can grant this privilege to any 
user, group, or well-known group, but creating permanent objects should 
generally be limited to drivers and system services. An object can be reverted 
back to a temporary object via NtMakeTemporaryObject(handle).

A named section object (i.e. file mapping) can also be created as a global 
name, i.e. r"Global\{object name}", which is accessible to all sessions. This 
requires SeCreateGlobalPrivilege, which by default is granted to system service 
accounts and administrators. This is separate from whether the section is 
temporary or permanent, but a permanent section object is more likely to be 
needed in the global namespace.

---

[1] 
https://docs.microsoft.com/en-us/windows/win32/api/ntdef/nf-ntdef-initializeobjectattributes
[2] 
https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwmaketemporaryobject

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46888>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46791] Allow os.remove to defer to rmdir

2022-03-01 Thread Eryk Sun


Eryk Sun  added the comment:

glibc remove() has an optimization to skip calling rmdir() if the macro 
condition IS_NO_DIRECTORY_ERROR is true for the unlink() error. For Linux, this 
condition is `errno != EISDIR`. On other platforms (e.g. BSD systems), the 
condition is `errno != EPERM`. The implementation is the following, from 
"sysdeps/posix/remove.c":

int
remove (const char *file)
{
  /* First try to unlink since this is more frequently the necessary 
action. */
  if (__unlink (file) != 0
  /* If it is indeed a directory...  */
  && (IS_NO_DIRECTORY_ERROR
  /* ...try to remove it.  */
  || __rmdir (file) != 0))
/* Cannot remove the object for whatever reason.  */
return -1;

  return 0;
}

WinAPI DeleteFileW() doesn't support the same distinction. In this case, 
ERROR_ACCESS_DENIED is set for the following system status codes:

STATUS_ACCESS_DENIED (like EACCES)
STATUS_CANNOT_DELETE (like EPERM; readonly or image-mapped)
STATUS_DELETE_PENDING (like EPERM)
STATUS_FILE_IS_A_DIRECTORY (like EISDIR)

os.remove() could skip skip calling RemoveDirectoryW() for a sharing violation, 
i.e. ERROR_SHARING_VIOLATION. If DeleteFileW() fails with a sharing violation, 
the path is not a directory and, even if it were, an attempt to delete it would 
fail.

--

___
Python tracker 
<https://bugs.python.org/issue46791>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46791] Allow os.remove to defer to rmdir

2022-02-28 Thread Eryk Sun


Eryk Sun  added the comment:

> For REMOVE_BOTH, I don't see the need of calling GetFileAttributes

I was thinking that the NtQueryAttributesFile() system call is relatively cheap 
compared to a full open, especially if the attributes of a remote file are 
cached locally. However, on second thought, it's probably not. An open can fail 
as soon as it knows that there's a file/directory type mismatch. This should be 
able to take advantage of a local attribute cache instead of incurring network 
latency.

> so I think there's no reason to combine it with the other two.

REMOVE_DIR can be separate, for the current behavior. But I wanted all modes 
handled in one function in case later on we decide to fix os.rmdir() in 
Windows. It allows deleting a directory symlink. Note that the os.lstat() 
result reports a directory symlink as an S_IFLNK file, not S_IFDIR, so the 
os.rmdir() behavior is internally inconsistent. This could be corrected by 
forcing the REMOVE_DIR mode to raise NotADirectoryError. For example:

} else { // mode != REMOVE_BOTH

WIN32_FIND_DATAW data;
BOOL isDir = FALSE;
BOOL isLnk = FALSE;

HANDLE hFind = FindFirstFileW(path->wide, );
if (hFind != INVALID_HANDLE_VALUE) {
FindClose(hFind);
isDir = data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
if (mode == REMOVE_DIR) {
isLnk = data.dwReserved0 == IO_REPARSE_TAG_SYMLINK;
} else {
isLnk = IsReparseTagNameSurrogate(data.dwReserved0);
}
}
}

if ((mode == REMOVE_DIR) && (isDir && isLnk)) {
SetLastError(ERROR_DIRECTORY); // POSIX ENOTDIR
} else if ((mode == REMOVE_DIR) || (isDir && isLnk)) {
success = RemoveDirectoryW(path->wide);
} else {
success = DeleteFileW(path->wide);
}
}

--

___
Python tracker 
<https://bugs.python.org/issue46791>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46869] platform.release() and sys returns wrong version on Windows 11

2022-02-26 Thread Eryk Sun


Eryk Sun  added the comment:

platform.release() returns platform.uname().release, which comes from 
platform.win32_ver() in Windows [1]. The issue with Windows 11 is being 
discussed in bpo-45382, but no PR has been submitted yet to resolve the issue.

> >>> sys.getwindowsversion().platform_version
> (10, 0, 22000)

This result is correct [2], but you should use platform.version() instead. The 
major.minor version of Windows 11 is 10.0. The build number for Windows 11 is 
22000 and above. The latest build number for Windows 10, in contrast, is 19044.

The system version number concerns API compatibility, not user-interface 
updates or marketing names. Windows XP, XP Professional x64 Edition, Vista, 7, 
8, and 8.1 correspond respectively to Windows system versions 5.1, 5.2, 6.0, 
6.1, 6.2, and 6.3. System versions 7.x, 8.x, and 9.x were skipped. Version 10.0 
is the first system version with two client workstation releases, Windows 10 
and Windows 11. It's thus the first time that the build number is required to 
differentiate the release name.

The `platform_version` attribute of the sys.getwindowsversion() result is 
supposed to be the true OS version in case the API is using a 
compatibility-mode version. It's based on the product version of the system 
file "kernel32.dll". However, it turns out that this is also not reliably the 
true OS version. The sys documentation was updated with a note that suggests 
using the platform module instead [3].

---

[1] https://docs.python.org/3/library/platform.html#platform.win32_ver
[2] https://en.wikipedia.org/wiki/Windows_11_version_history#Version_history
[3] https://docs.python.org/3/library/sys.html#sys.getwindowsversion

--
nosy: +eryksun
resolution:  -> duplicate
stage:  -> resolved
status: open -> closed
superseder:  -> platform() is not able to detect windows 11

___
Python tracker 
<https://bugs.python.org/issue46869>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue28824] os.environ should preserve the case of the OS keys ?

2022-02-26 Thread Eryk Sun


Eryk Sun  added the comment:

> I think there should be a public class like this.

I wrote a basic implementation of _CaseInsensitiveString under the assumption 
that it's hidden behind the __getitem__(), __setitem__(), and __delitem__() 
methods of the _Environ class. I don't want to complicate the implementation.

The problem of case-insensitive names in file/registry paths and environment 
variables should be addressed with ntpath.normcase(). This should be based on 
WinAPI LCMapStringEx() with LOCALE_NAME_INVARIANT and LCMAP_UPPERCASE. The 
current implementation of ntpath.normcase() uses str.lower(), which depends on 
Python's Unicode database. This possibly differs from what Windows considers to 
be lower case in the invariant locale. It's also wrong because Windows uses 
upper case for cases-insensitive comparisons, which matters when sorting names. 
See bpo-42658.

> Right now, it's not a good workaround because it contains the 
> environment at the time the interpreter was started, not the 
> current environment.

Note that in some cases the "current environment" is the process environment, 
which isn't necessarily consistent with os.environ on any platform.

In POSIX, posix.environ is created from C environ, which is kept in sync with 
changes to posix.environ via C putenv() and unsetenv(). However, directly 
calling os.putenv() or os.unsetenv(), or the underlying C functions, modifies 
the process environment without changing posix.environ. If subprocess.Popen() 
is called without overriding env, the child inherits the process environment, 
not posix.environ.

In Windows, os.environ is created from nt.environ, with variables names 
converted to upper case. nt.environ is created from C _wenviron, which is 
created from the process environment, as returned by WinAPI 
GetEnvironmentStringsW(), except with variable names that begin with "=" 
filtered out. os.putenv() and os.unsetenv() are based on C _wputenv(), which 
updates C _wenviron and also updates the process environment via WinAPI 
SetEnvironmentVariableW(). If either os.putenv() or os.unsetenv() is called 
directly, or _wputenv() at a lower level, then the variables in C _wenviron 
(not just the letter case of the names) will be out of sync with os.environ. 
Additionally, if SetEnvironmentVariableW() is called directly to set or unset a 
variable, then both os.environ and C _wenviron will be out of sync with the 
process environment. If subprocess.Popen() is called without overriding env, 
the child inherits the process environment, not os.environ or C _wenviron.

--

___
Python tracker 
<https://bugs.python.org/issue28824>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46861] os.environ forces variable names to upper case on Windows

2022-02-25 Thread Eryk Sun


Change by Eryk Sun :


--
resolution:  -> duplicate
stage:  -> resolved
status: open -> closed
superseder:  -> os.environ should preserve the case of the OS keys ?

___
Python tracker 
<https://bugs.python.org/issue46861>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue28824] os.environ should preserve the case of the OS keys ?

2022-02-25 Thread Eryk Sun


Change by Eryk Sun :


--
versions: +Python 3.11

___
Python tracker 
<https://bugs.python.org/issue28824>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46862] subprocess makes environment blocks with duplicate keys on Windows

2022-02-25 Thread Eryk Sun


Eryk Sun  added the comment:

I suggest closing this issue as a duplicate of bpo-43702.

--

___
Python tracker 
<https://bugs.python.org/issue46862>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue43702] [Windows] correctly sort and remove duplicates in _winapi getenvironment()

2022-02-25 Thread Eryk Sun


Change by Eryk Sun :


--
versions: +Python 3.11 -Python 3.8

___
Python tracker 
<https://bugs.python.org/issue43702>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46862] subprocess makes environment blocks with duplicate keys on Windows

2022-02-25 Thread Eryk Sun


Eryk Sun  added the comment:

This should be handled in _winapi.CreateProcess(). An environment block is 
technically required to be sorted. (Ages ago this was a MUST requirement for 
getting and setting variables to work correctly, since the implementation 
depended on the sort order, but I think nowadays it's a SHOULD requirement.) 
For example, see the documentation of CreateProcessW() [1]:

If an application provides an environment block, ... explicitly create
these environment variable strings, sort them alphabetically (because
the system uses a sorted environment)

"Changing Environment Variables" is more specific [2]:

All strings in the environment block must be sorted alphabetically by name.
The sort is case-insensitive, Unicode order, without regard to locale.

CompareStringOrdinal() [3] implements a case-insensitive ordinal comparison. 
When a key compares as equal, either keep the current one in the sorted list, 
or replace it with the new key.

---

[1] 
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
[2] 
https://docs.microsoft.com/en-us/windows/win32/procthread/changing-environment-variables
[3] 
https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-comparestringordinal

--
nosy: +eryksun
versions: +Python 3.11, Python 3.9

___
Python tracker 
<https://bugs.python.org/issue46862>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46858] mmap constructor resets the file pointer on Windows

2022-02-25 Thread Eryk Sun


Eryk Sun  added the comment:

The resize() method also modifies the file pointer. Instead of fixing that 
oversight, I think it should directly set the file's FileEndOfFileInfo and 
FileAllocationInfo. For example:

// resize the file
if (!SetFileInformationByHandle(
self->file_handle, FileEndOfFileInfo,
_size, sizeof(max_size)) ||
  !SetFileInformationByHandle(
self->file_handle, FileAllocationInfo,
_size, sizeof(max_size)))
{
// resizing failed. try to remap the file
file_resize_error = GetLastError();
max_size.QuadPart = self->size;
new_size = self->size;
}

This is cheaper in terms of system calls. The existing implementation makes 
four system calls: one to set the file pointer in SetFilePointerEx() and three 
in SetEndOfFile(), which queries the file pointer, sets the end-of-file info, 
and sets the allocation info. 

Note that this approach doesn't modify the file pointer in any case. This may 
be surprising if the file size shrinks to less than the existing file pointer. 
But os.ftruncate() behaves the same way, as does the resize() method in Linux.

--
nosy: +eryksun
versions:  -Python 3.7, Python 3.8

___
Python tracker 
<https://bugs.python.org/issue46858>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46855] printing a string with strange characters loops forever

2022-02-25 Thread Eryk Sun


Eryk Sun  added the comment:

The ordinal range 0x80-0x9F is the C1 control code set [1]. Ordinal 0x9F is 
"Application Program Command" (APC). The command must be terminated by ordinal 
0x9C, "String Terminator" (ST). For example, "\x9f Some Command \x9c". 

In Gnome Terminal, after executing print('\x9f'), an APC command without a 
terminator, typing Ctrl+L still works to clear the screen and get back to a 
prompt.

[1] https://en.wikipedia.org/wiki/C0_and_C1_control_codes#C1_controls

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46855>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue37426] getpass.getpass not working with on windows when ctrl+v is used to enter the string

2022-02-25 Thread Eryk Sun


Eryk Sun  added the comment:

> I have an idea to solve it. But I don't know how to get the 
> clipboard data.

In Windows, using the window manager entails extending the process and the 
current thread with GUI-related structures in the kernel and then connecting 
the process to a window station (usually "WinSta0", which contains the 
clipboard) and connecting the thread to a desktop (usually "Default"). This 
permanently changes how the OS sees the process. I think whether or not the 
process should be a GUI process is something for the application to decide, not 
the standard library. Thus getpass should not read text from the clipboard.

The docs could note that terminals may need to be configured to support 
Ctrl+Shift+C (copy) and Ctrl+Shift+V (paste) shortcuts, and that some terminals 
provide alternate ways to paste text, such as a right-click action or context 
menu. I don't think the docs should provide detailed explanations and 
configuration details for particular terminals.

--

___
Python tracker 
<https://bugs.python.org/issue37426>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue37426] getpass.getpass not working with on windows when ctrl+v is used to enter the string

2022-02-25 Thread Eryk Sun


Eryk Sun  added the comment:

> Clicking `Edit > Paste` from the window menu
> Use right-click to paste

In particular, if the console has quick-edit mode enabled, then you can paste 
text by right-clicking. Also, if text is selected in quick-edit mode, 
right-clicking copies to the clipboard.

> Enabling `Properties > Options > Use Ctrl+Shift+C/V as Copy/Paste` from the 
> menu

I prefer this setting because it matches the behavior of terminals on other 
platforms. I suggest setting it in the "Defaults" dialog instead of 
"Properties". Otherwise, you'll have to configure it individually for each 
shell link (.LNK file) or session title. (The console session title defaults to 
the executable path, unless set in STARTUPINFO.)

> Using the new Windows Terminal

Terminal allows configuring the actions for keyboard shortcuts. By default it 
grabs Ctrl+C (copy) and Ctrl+V (paste). I disable these mappings. I leave the 
default mappings in place for Ctrl+Shift+C (copy), Ctrl+Shift+V (paste), 
Ctrl+Insert (copy), and Shift+Insert (paste).  

> This behavior is the same Command Prompt and PowerShell

The behavior has to be the same when the parent process is a normal console 
shell such as CMD or PowerShell. Python inherits its console session from the 
shell, and that's the extent of the shell's involvement. 

A console session is hosted by an instance of conhost.exe or openconsole.exe. 
If the host is running in pseudoconsole (headless) mode, then the user 
interface for the session is hosted by another application (e.g. Windows 
Terminal). Even in pseudoconsole mode, however, the console host a lot to do in 
order to manage the session state for the console API.

--

___
Python tracker 
<https://bugs.python.org/issue37426>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue12165] [doc] clarify documentation of nonlocal

2022-02-24 Thread Eryk Sun


Eryk Sun  added the comment:

> Another problem with the current text is that it fails to exclude 
> enclosing class scopes

The nonlocal statement is only disallowed in module code (i.e. "exec" 
compilation mode) because it can never be nested. It's allowed in a class 
definition that has an outer function scope. A class body itself is never a 
nonlocal scope; it just has access to them. Here's a slightly modified version 
that includes class definitions:

"When the definition of a function or class is nested (enclosed) within the 
definitions of other functions, its nonlocal scopes are the local scopes of the 
enclosing functions. The nonlocal statement causes the listed identifiers to 
refer to names previously bound in nonlocal scopes. If a name is bound in more 
than one nonlocal scope, the nearest binding is used. If a name is not bound in 
any nonlocal scope, or if there is no nonlocal scope, a SyntaxError is raised.

The nonlocal statement applies to the entire scope of a function or class body. 
A SyntaxError is raised if a variable is used or assigned to prior to its 
nonlocal declaration in the scope."

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue12165>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46839] Process finished with exit code -1073741819 (0xC0000005)

2022-02-23 Thread Eryk Sun


Eryk Sun  added the comment:

I tried to get more information for you by installing 2.7.11 (64-bit because of 
the given fault) and unassembling python27.dll at the fault offset. But it 
doesn't help. Whatever the bug is, it ended up jumping to an address that's in 
the middle of an instruction. This final bit of nonsense is what killed the 
process with an access violation, but it's not directly related to the problem.

Here's the address that triggered the access violation:

0:000> ? python27 + a4f81
Evaluate expression: 1368149889 = `518c4f81

The nearest function is rawiobase_readall():

0:000> ln python27 + a4f81
(`518c4f10)   python27!rawiobase_readall+0x71   |
(`518c51a0)   python27!resize_buffer

But 0x518c4f81 isn't the address of an instruction in this function:

0:000> u python27!rawiobase_readall python27!rawiobase_readall+80
python27!rawiobase_readall:
[...]
`518c4f7f 48894318mov qword ptr [rbx+18h],rax
`518c4f83 48894310mov qword ptr [rbx+10h],rax
`518c4f87 48894320mov qword ptr [rbx+20h],rax
`518c4f8b 48837f10fe  cmp qword ptr 
[rdi+10h],0FFFEh
`518c4f90 4c89642440  mov qword ptr [rsp+40h],r12

It's inside of the first MOV instruction.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46839>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46733] pathlib.Path methods can raise NotImplementedError

2022-02-21 Thread Eryk Sun


Eryk Sun  added the comment:

> pathlib does not allow to distinguish "path" from "path/".

os.path.normpath() and os.path.abspath() don't retain a trailing slash -- or a 
leading dot component for that matter. Are you referring to os.path.join()? For 
example:

>>> os.path.join('./spam', 'eggs/')
'./spam/eggs/'
>>> os.path.normpath('./spam/eggs/')
'spam/eggs'
>>> PurePath('./spam') / PurePath('eggs/')
PurePosixPath('spam/eggs')

A leading dot component is significant in a context that searches a set of 
paths -- usually PATH. A trailing slash is significant in a context that has to 
distinguish a device or file path from a directory path, of which there are 
several cases in Windows.

I think it's a deficiency in pathlib that it lacks a way to require 
conservative normalization in these cases. Path and PurePath objects could gain 
a keyword-only parameter, and internal attribute if needed, that enables a more 
conservative normalization.

--

___
Python tracker 
<https://bugs.python.org/issue46733>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46791] Allow os.remove to defer to rmdir

2022-02-19 Thread Eryk Sun


Eryk Sun  added the comment:

In Windows, checking for a directory in order to defer to either 
os_rmdir_impl() or os_unlink_impl() would lead to a redundant directory check. 
os_unlink_impl() already has to check for a directory in order to delete a 
directory symlink or mountpoint. I suggest implementing a common _Py_remove() 
function in Windows. For example:

typedef enum {
REMOVE_FILE,
REMOVE_DIR,
REMOVE_BOTH,
} _Py_remove_mode;

int
_Py_remove(path_t *path, _Py_remove_mode mode)
{
BOOL isDir = FALSE;
BOOL isLnk = FALSE;
BOOL success = FALSE;

if (mode != REMOVE_DIR) {
DWORD fileAttributes = GetFileAttributesW(path->wide);
if (fileAttributes != INVALID_FILE_ATTRIBUTES) {
isDir = fileAttributes & FILE_ATTRIBUTE_DIRECTORY;
}

if (isDir && (mode == REMOVE_FILE)) {
WIN32_FIND_DATAW data;
HANDLE hFind = FindFirstFileW(path->wide, );
if (hFind != INVALID_HANDLE_VALUE) {
FindClose(hFind);
if (data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
isLnk = IsReparseTagNameSurrogate(data.dwReserved0);
}
}
}
}

if (mode == REMOVE_DIR || isDir && (mode == REMOVE_BOTH || isLnk)) {
success = RemoveDirectoryW(path->wide);
} else {
success = DeleteFileW(path->wide);
}

return success ? 0 : -1;
}

The multiple opens for GetFileAttributesW(), FindFirstFileW() and DeleteFileW() 
add up to make the delete more expensive and more race prone than it has to be. 
It would be nice to use a single open for all operations. But the Windows API 
just has CreateFileW(), which requires SYNCHRONIZE access. The latter can't be 
granted by the parent directory, unlike FILE_READ_ATTRIBUTES and DELETE access. 
We could implement a version that uses CreateFileW(), and fall back on the 
above version when access is denied, similar to what we do for os.stat(). Also, 
Python is limited to the Windows 8.1 SDK, which makes it awkward to use 
FileDispositionInfoEx (POSIX delete) like DeleteFileW() does in Windows 10, but 
it should still be possible.

--
nosy: +eryksun
stage:  -> needs patch
versions: +Python 3.11

___
Python tracker 
<https://bugs.python.org/issue46791>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46785] On Windows, os.stat() can fail if called while another process is creating or deleting the file

2022-02-18 Thread Eryk Sun


Eryk Sun  added the comment:

Windows filesystems disallow new opens for a file that has its delete 
disposition set (i.e. the file is marked for deletion). For example, 
CreateFileW() fails with ERROR_ACCESS_DENIED (5) in this case. A file with its 
delete disposition set is still visibly linked in its parent directory. 

The trigger to unlink a file that's marked for deletion depends on the delete 
mode. In Windows 10, DeleteFileW() uses a POSIX delete if it's supported by the 
filesystem (e.g. NTFS). This delete mode unlinks the file as soon the file 
object that was used to set the delete disposition is closed. There's still a 
small window of time, however, in which attempts to open the file will fail 
with an access-denied error.

In os.stat(), if CreateFileW() fails with access denied, FindFirstFileW() is 
called to try to get the stat data from the file's parent directory. But in 
this case it's likely that the file has been unlinked from the directory by the 
time FindFirstFileW() is called.

The original error code from CreateFileW() gets restored if FindFirstFileW() 
fails. This is generally the right thing to do. However, if FindFirstFileW() 
fails with ERROR_FILE_NOT_FOUND (2) or ERROR_PATH_NOT_FOUND (3), then I suggest 
that the previous error code should not be restored.

For example:

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, , )) {
/* Cannot read the parent directory. */
DWORD dir_error = GetLastError();
if (dir_error != ERROR_FILE_NOT_FOUND &&
  dir_error != ERROR_PATH_NOT_FOUND) {
SetLastError(error);
}
return -1;
}

--
nosy: +eryksun
stage:  -> needs patch
type:  -> behavior
versions: +Python 3.11, Python 3.9

___
Python tracker 
<https://bugs.python.org/issue46785>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46763] os.path.samefile incorrect results for shadow copies

2022-02-16 Thread Eryk Sun


Eryk Sun  added the comment:

Sample implementation:

import os
import msvcrt
import win32file 

def samefile(f1, f2):
"""Test whether two paths refer to the same file or directory."""
s1 = os.stat(f1)
s2 = os.stat(f2)
return _common_same_file(f1, f2, s1, s2)


def sameopenfile(fd1, fd2):
"""Test whether two file descriptors refer to the same file."""
s1 = os.fstat(fd1)
s2 = os.fstat(fd2)
return _common_same_file(fd1, fd2, s1, s2)


def _common_same_file(f1, f2, s1, s2):
if s1.st_ino != s2.st_ino or s1.st_dev != s2.st_dev:
return False

# (st_dev, st_ino) may be insufficient on its own. Use the final
# NT path of each file to refine the comparison.
p = _get_final_nt_paths([f1, f2])

# The stat result is unreliable if the volume serial number (st_dev)
# or file ID (st_ino) is 0.
if 0 in (s1.st_dev, s1.st_ino):
if None in p:
return False
return p[0] == p[1]

# A volume shadow copy has the same volume serial number as the
# base volume. In this case, the device names have to be compared.
d = _get_device_names(p)
if any('volumeshadowcopy' in n for n in d if n):
return d[0] == d[1]

return True


def _get_final_nt_paths(files):
result = []
nt_normal = 0x2 # VOLUME_NAME_NT | FILE_NAME_NORMALIZED
nt_opened = 0xA # VOLUME_NAME_NT | FILE_NAME_OPENED
for f in files:
p = None
if f is not None:
try:
p = _getfinalpathname(f, nt_normal)
except OSError:
try:
p = _getfinalpathname(f, nt_opened)
except OSError:
pass
result.append(p)
return result


def _get_device_names(paths):
# Look for "\Device\{device name}[\]".
result = []
for p in paths:
d = None
if p is not None:
q = p.split('\\', 3)
if len(q) > 2 and q[1].lower() == 'device' and q[2]:
d = q[2].lower()
result.append(d)
return result


def _getfinalpathname(p, flags=0):
try:
if isinstance(p, int):
h = msvcrt.get_osfhandle(p)
else:
h = win32file.CreateFile(p, 0, 0, None, win32file.OPEN_EXISTING,
win32file.FILE_FLAG_BACKUP_SEMANTICS, None)
return win32file.GetFinalPathNameByHandle(h, flags)
except win32file.error as e:
strerror = e.strerror.rstrip('\r\n .')
raise OSError(0, strerror, p, e.winerror) from None

--

___
Python tracker 
<https://bugs.python.org/issue46763>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46775] [Windows] OSError should unconditionally call winerror_to_errno

2022-02-16 Thread Eryk Sun


New submission from Eryk Sun :

bpo-37705 overlooked fixing the OSError constructor. oserror_parse_args() in 
Objects/exceptions.c should unconditionally call winerror_to_errno(), which is 
defined in PC/errmap.h. 

winerror_to_errno() maps the Winsock range 1-11999 directly, except for the 
6 errors in this range that are based on C errno values: WSAEINTR, WSAEBADF, 
WSAEACCES, WSAEFAULT, WSAEINVAL, and WSAEMFILE. Otherwise, Windows error codes 
that aren't mapped explicitly get mapped by default to EINVAL.

--
components: Interpreter Core, Windows
keywords: easy (C)
messages: 413383
nosy: eryksun, paul.moore, steve.dower, tim.golden, zach.ware
priority: normal
severity: normal
stage: needs patch
status: open
title: [Windows] OSError should unconditionally call winerror_to_errno
type: behavior
versions: Python 3.10, Python 3.11, Python 3.9

___
Python tracker 
<https://bugs.python.org/issue46775>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46763] os.path.samefile incorrect results for shadow copies

2022-02-16 Thread Eryk Sun


Eryk Sun  added the comment:

Python uses the volume serial number (VSN) and file ID for st_dev and st_ino. 
The OS allows the file ID to be 0 if the filesystem doesn't support file IDs. 
Also, it does not require or force the VSN to be a unique ID in the system, 
though if it's not 0 it's almost always a random 32-bit number for which the 
chance of collision is vanishingly small (notwithstanding a volume shadow copy, 
apparently). This means that (st_dev, st_ino) by itself is not sufficient to 
check whether two paths are the same file in Windows.

Proposal:

When comparing two file paths, if their (st_dev, st_ino) values differ, then 
they're not the same file. If their (st_dev, st_ino) values are the same, use 
the final NT paths from calling GetFinalPathNameByHandleW() with the flags 
VOLUME_NAME_NT | FILE_NAME_NORMALIZED. If only one of the paths supports 
FILE_NAME_NORMALIZED, then they're not the same file. If neither supports 
FILE_NAME_NORMALIZED, fall back on VOLUME_NAME_NT | FILE_NAME_OPENED. If either 
st_dev is 0 or st_ino is 0, the files are the same only if the final NT paths 
are the same. Else split out each device path. If the device paths are the 
same, then the paths are the same file. Otherwise they're different files.

We should probably special case the comparison of a multiple UNC provider path 
with a local volume path. For example r'\\localhost\C$\Windows' is the same as 
r'C:\Windows'. The corresponding NT paths are 
r'\Device\Mup\localhost\C$\Windows' and typically 
r'\Device\HarddiskVolume2\Windows'. The special case is that when one of the 
device paths is "\Device\Mup", the two device paths are not required to be the 
same. Of course, this is given that the (st_dev, st_ino) values are the same, 
and neither st_dev nor st_ino is zero.

That said, we would need to exclude volume shadow copies from the special case. 
I suppose we could just look for "VolumeShadowCopy" in the device name. Maybe 
we can do better. I've noticed that querying IOCTL_STORAGE_GET_DEVICE_NUMBER 
fails for a volume shadow copy, but that's probably going overboard.

--
nosy: +eryksun
stage:  -> needs patch
versions: +Python 3.10, Python 3.11

___
Python tracker 
<https://bugs.python.org/issue46763>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46733] pathlib.Path methods can raise NotImplementedError

2022-02-15 Thread Eryk Sun


Eryk Sun  added the comment:

> I'm planning to learn more heavily on posixpath + ntpath in 
> pathlib once bpo-44136 is done. I think that would be a good 
> time to introduce is_mount() support on Windows.

In the long run, it would be better to migrate the implementations in the other 
direction. Rewrite genericpath, ntpath, posixpath, and parts of shutil to use 
PurePath and Path objects. Using path objects instead of generic strings should 
improve the implementation of os.path and shutil, in addition to ensuring 
consistency with pathlib. However, that's a long-term goal that doesn't 
preclude using os.path and shutil as needed now, such as relying on 
os.path.ismount().

--

___
Python tracker 
<https://bugs.python.org/issue46733>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46733] pathlib.Path methods can raise NotImplementedError

2022-02-14 Thread Eryk Sun


Eryk Sun  added the comment:

WindowsPath.is_mount() should call ntpath.ismount(). This function needs a 
significant redesign, but it's fine to use it as long as it's documented that 
is_mount() is equivalent to os.path.ismount().

Since the owner() and group() methods return names instead of numeric IDs, 
technically we could implement them in WindowsPath. That said, in Windows, 
st_mode and chmod() are unrelated to a file's owner and group, plus object 
ownership is fundamentally different from POSIX. Unless chown() is also 
implemented, I don't think we would gain much from knowing the owner and group, 
other than making it easier to display them.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46733>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46751] Windows-style path is not recognized under cygwin

2022-02-14 Thread Eryk Sun


Eryk Sun  added the comment:

MSYS2 has basically the same problem when the script is passed as a Windows 
path, except it uses "/c" for the "C:" drive instead of "/cygdrive/c".

# python3 -VV
Python 3.9.9 (main, Dec 28 2021, 11:05:23)
[GCC 11.2.0]

# echo $PWD
/proc

# python3 C:/Temp/test.py
python3: can't open file '/proc/C:/Temp/test.py': [Errno 2] No such file or 
directory

Windows paths in the command-line arguments appear to be passed without 
conversion:

# python3 -ic 1 C:/Temp/test.py
>>> import os
>>> open(f'/proc/{os.getpid()}/cmdline').read().split('\0')
['python3', '-ic', '1', 'C:/Temp/test.py', '']

They're generally supported:

>>> open('C:/Temp/test.py').read()
'import sys\nprint(sys.executable)\n\n'

>>> os.path.samefile('C:/Temp/test.py', '/c/Temp/test.py')
True

>>> os.path.abspath('C:/Temp/test.py')
'C:/Temp/test.py'

realpath() doesn't support them:

>>> os.path.realpath('C:/Temp/test.py')
'/:/Temp/test.py'

But the C API _Py_wrealpath() does:

>>> import ctypes
>>> path = (ctypes.c_wchar * 1000)()
>>> ctypes.pythonapi._Py_wrealpath('C:/Temp/test.py', path, 1000)
1484496
>>> path.value
'/c/Temp/test.py'

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46751>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46716] regrtest didn't respect the timeout when running test_subprocess on AMD64 Windows11 3.x

2022-02-14 Thread Eryk Sun


Eryk Sun  added the comment:

> 4) use Job objects to group Windows processes for termination

I think a separate issue should be created for this enhancement.

_winapi wrappers would be needed for CreateJobObjectW(), 
SetInformationJobObject(), AssignProcessToJobObject(), TerminatejobObject(), 
and ResumeThread(), plus the constant CREATE_SUSPENDED. I'd also prefer to make 
related changes to send_signal(), which would require GetConsoleProcessList() 
and GenerateConsoleCtrlEvent().

A new Popen option would be needed to configure whether the job allows 
descendants to break away via the process creation flag 
CREATE_BREAKAWAY_FROM_JOB. This should be allowed by default.

---

send_signal(): SIGKILL, SIGTERM, SIBREAK, SIGINT

Support Popen.kill(group=False) and Popen.terminate(group=False) on all 
platforms as Popen.send_signal(signal.SIGKILL, group=group) and 
Popen.send_signal(signal.SIGTERM, group=group).

The Universal CRT (ucrt) in Windows doesn't define SIGKILL. Even when it's not 
defined by the platform, signal.SIGKILL should always be defined, preferably as 
9 (unused by ucrt), else as an unused value in the range up to NSIG, else as 
NSIG + 1.

The `group` keyword-only option broadens the scope to the process group or job. 
A process is a group leader if it was created with the flag 
CREATE_NEW_PROCESS_GROUP (save self._creationflags) and its process ID is in 
GetConsoleProcessList().

For SIGKILL, always use forced termination. For SIGTERM, use forced termination 
either if `group` is false or if `group` is true and the process is not a group 
leader. To force termination, call TerminateJobObject(self._job_handle, 1) if 
`group` is true, else TerminateProcess(self._handle, 1). 

For SIGTERM, SIGBREAK, and SIGINT, call GenerateConsoleCtrlEvent() if `group` 
is true and the process is a group leader. For SIGTERM and SIGBREAK, send 
CTRL_BREAK_EVENT. For SIGINT, send CTRL_C_EVENT. 

Behavior to deprecate:

When `group` is false and `sig` is CTRL_C_EVENT (0) or CTRL_BREAK_EVENT (1), 
send_signal() always calls os.kill(). This legacy behavior tries to handle a 
process as if it's a process group, and it combines POSIX kill() with Windows 
API constants. subprocess should distance itself from the fundamental problems 
with os.kill() in Windows.

--

___
Python tracker 
<https://bugs.python.org/issue46716>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46716] regrtest didn't respect the timeout when running test_subprocess on AMD64 Windows11 3.x

2022-02-14 Thread Eryk Sun


Eryk Sun  added the comment:

> I fear the potential 3rd-party breakage alone should bump
> this to its own issue.

The change to the DWORD converter in _winapi should only be in 3.11+. If this 
causes problems for other projects, they're probably depending on undefined 
behavior in the standard library. No third-party package or application should 
use _winapi directly.

> 1) modify Windows Popen._wait() to raise on out of bounds 
> values [< 0 or >= INFINITE]

I think raising ValueError would be best at this level. For example:

if timeout is None:
timeout_millis = _winapi.INFINITE
else:
timeout_millis = int(timeout * 1000)
if timeout_millis >= _winapi.INFINITE:
raise ValueError("'timeout' exceeds the platform limit")
if timeout_millis < 0:
raise ValueError("'timeout' must be non-negative")

--

___
Python tracker 
<https://bugs.python.org/issue46716>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46726] Thread spuriously marked dead after interrupting a join call

2022-02-13 Thread Eryk Sun


Eryk Sun  added the comment:

> I didn't have that in mind at all.

I understood what you had in mind, and I don't disagree. I was just 
highlighting that the only somewhat important work done in _stop() is to clean 
up the _shutdown_locks set, to keep it from growing too large. But that task is 
already handled when creating a new thread, and it's arguably more important to 
handle it there.

--

___
Python tracker 
<https://bugs.python.org/issue46726>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46726] Thread spuriously marked dead after interrupting a join call

2022-02-13 Thread Eryk Sun


Eryk Sun  added the comment:

> Anything at the Python level that cares whether the thread is 
> still alive will call _wait_for_tstate_lock() again

It's nice that _maintain_shutdown_locks() gets called in _stop(), but the more 
important call site is in _set_tstate_lock().

I typed up the example initially with try/finally. Then I changed it to avoid 
the extra lock.locked() call when there's no exception, but I forgot to add a 
`raise` statement:

try:
if lock.acquire_and_release(block, timeout):
self._stop
except:
if not lock.locked():
self._stop()
raise

--

___
Python tracker 
<https://bugs.python.org/issue46726>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46726] Thread spuriously marked dead after interrupting a join call

2022-02-13 Thread Eryk Sun


Eryk Sun  added the comment:

> is there a bulletproof way to guarantee that `self._stop()` gets 
> called if the acquire_and_release() succeeds? 

I don't think it's critical. But we still should deal with the common case in 
Windows in which a KeyboardInterrupt is raised immediately after the method 
returns. For example:

try:
if lock.acquire_and_release(block, timeout):
self._stop
except:
if not lock.locked():
self._stop()

The proposed acquire_and_release() method can also be used in 
threading._shutdown(), when it iterates _shutdown_locks to join non-daemon 
threads.

--

___
Python tracker 
<https://bugs.python.org/issue46726>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46716] regrtest didn't respect the timeout when running test_subprocess on AMD64 Windows11 3.x

2022-02-13 Thread Eryk Sun


Eryk Sun  added the comment:

> the fix should be as simple as coercing the timeout values to >= 0.

Popen._remaining_time() should return max(endtime - _time(), 0).

Popen._wait() should raise OverflowError if the timeout is too large for the 
implementation. In Windows, the upper limit in milliseconds is 
`_winapi.INFINITE - 1` (about 49.7 days). It's important to only allow the 
timeout in milliseconds to be _winapi.INFINITE when `timeout is None`.

The DWORD converter in _winapi needs to subclass unsigned_long_converter. The 
current implementation based on the legacy format unit "k" is too lenient. 
Negative values and values that are too large should fail. I updated it to use 
the following definition:

class DWORD_converter(unsigned_long_converter):
type = 'DWORD'

This produces the following improved results:

>>> h = _winapi.GetCurrentProcess()
>>> _winapi.WaitForSingleObject(h, _winapi.INFINITE + 1)
Traceback (most recent call last):
  File "", line 1, in 
OverflowError: Python int too large to convert to C unsigned long

>>> _winapi.WaitForSingleObject(h, -1)
Traceback (most recent call last):
  File "", line 1, in 
ValueError: value must be positive

--

___
Python tracker 
<https://bugs.python.org/issue46716>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46726] Thread spuriously marked dead after interrupting a join call

2022-02-12 Thread Eryk Sun


Eryk Sun  added the comment:

> Wrap everything needed in a custom C function. 

Maybe add an `acquire_and_release()` method:

static PyObject *
lock_PyThread_acquire_and_release_lock(
lockobject *self, PyObject *args, PyObject *kwds)
{
_PyTime_t timeout;

if (lock_acquire_parse_args(args, kwds, ) < 0)
return NULL;

PyLockStatus r = acquire_timed(self->lock_lock, timeout);

if (r == PY_LOCK_INTR) {
return NULL;
}

if (r == PY_LOCK_ACQUIRED) {
PyThread_release_lock(self->lock_lock);
self->locked = 0;
}

return PyBool_FromLong(r == PY_LOCK_ACQUIRED);
}

--

___
Python tracker 
<https://bugs.python.org/issue46726>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46726] Thread spuriously marked dead after interrupting a join call

2022-02-12 Thread Eryk Sun


Eryk Sun  added the comment:

> If the acquire() in fact times out, but the store to the `acquired` 
> variable is interrupted, `if _WINDOWS and acquired is None` will
> succeed, despite that the lock is still locked

Yeah, my proposed workaround is no good, so we can't resolve this without a 
more fundamental solution. Are you looking into a way to prevent the STORE_FAST 
instruction from getting interrupted by an asynchronous exception?

--

___
Python tracker 
<https://bugs.python.org/issue46726>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46726] Thread spuriously marked dead after interrupting a join call

2022-02-12 Thread Eryk Sun


Eryk Sun  added the comment:

> The race on := is much smaller than the original race 
> and I suspect in practice will be very hard to hit.

In Windows, the acquire() method of a lock can't be interrupted. Thus, in the 
main thread, an exception from Ctrl+C gets raised as soon as acquire() returns. 
This exception definitely will interrupt the assignment. Here's a workaround:

global scope:

_WINDOWS = _sys.platform == 'win32'

in _wait_for_tstate_lock():

acquired = None

try:
if acquired := lock.acquire(block, timeout):
lock.release()
self._stop()
except:
if _WINDOWS and acquired is None:
acquired = True
if acquired:
lock.release()
self._stop()
raise

This doesn't help in POSIX if the STORE_FAST instruction that assigns 
`acquired` gets interrupted. This can't be distinguished from acquire() itself 
getting interrupted. But at least the window for this is as small as possible.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46726>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46716] regrtest didn't respect the timeout when running test_subprocess on AMD64 Windows11 3.x

2022-02-11 Thread Eryk Sun


Eryk Sun  added the comment:

> test_call_timeout() or test_timeout() in test_subprocess.py.

These tests don't override the standard files, and they only spawn a single 
child with no descendants. I don't see why this would hang. It shouldn't be a 
problem with leaked pipe handles (see bpo-43346). It probably will need to be 
diagnosed by attaching a debugger, or offline with a dump file.

> process trees whereas terminating a parent automatically kills the children

One can use a job object to manage a child process and all of its descendants, 
including resource usage and termination. A process can belong to multiple job 
objects in Windows 8+, which is required by Python 3.9+.

For reliability, the child has to be created in a suspended state via 
CREATE_SUSPENDED. It can be resumed with ResumeThread() after adding it to the 
job with AssignProcessToJobObject().

You can try to terminate a job cleanly, which is similar in effect to sending 
SIGTERM to a process group in POSIX. In Windows, this has to be approached 
differently for console vs graphical processes.

To handle console apps, assuming the child inherits the current console, spawn 
it as a new process group via 
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP. You can request an exit by 
sending a Ctrl+Break event to the group via os.kill(p.pid, 
signal.CTRL_BREAK_EVENT) [1]. The request might be ignored, but typically the 
default handler is called, which calls ExitProcess().

To handle GUI apps, assuming the child inherits the current desktop (usually 
"WinSta0\Default"), first enumerate the top-level and message-only windows on 
the current desktop via EnumWindows() and FindWindowExW(). Use 
GetWindowThreadProcessId() to filter the list to include only windows that 
belong to the job. Post WM_CLOSE to each window in the job. A process might 
ignore a request to close. It could keep the window open or continue running in 
the background.

After an internal timeout, you can call TerminateJobObject() to kill any 
process in the job that remains alive. This is a forced and abrupt termination, 
which is similar to sending SIGKILL to a process group in POSIX.

---

[1] This usage of os.kill() is what we're stuck with. Rightfully, we should be 
using os.killpg(p.pid, signal.SIGBREAK) or os.kill(-p.pid, signal.SIGBREAK) 
(note the negative pid value).

--

___
Python tracker 
<https://bugs.python.org/issue46716>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46703] boolean operation issue (True == False == False)

2022-02-09 Thread Eryk Sun


Eryk Sun  added the comment:

> True == False == False is really True == False and False == False 
> wich is False and True which is False

Moreover, since the left-hand comparison is `True == False`, which evaluates to 
False, the right-hand comparison doesn't even get evaluated. 

In the following example, print(3) doesn't get called because the left-hand 
comparison, `None != None`, is False:

>>> print(1) != print(2) == print(3)
1
2
False

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46703>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46697] _ctypes_simple_instance returns inverted logic

2022-02-09 Thread Eryk Sun


Change by Eryk Sun :


--
stage:  -> patch review
versions:  -Python 3.7, Python 3.8

___
Python tracker 
<https://bugs.python.org/issue46697>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46686] [venv / PC/launcher] issue with a space in the installed python path

2022-02-08 Thread Eryk Sun


Eryk Sun  added the comment:

The venv launcher can quote the executable path either always or only when it 
contains spaces. The following is a suggestion for implementing the latter.

Before allocating `executable`, use memchr() (include ) to search the 
UTF-8 source for a space. If found, increment the character count by two. After 
allocating `executable`, add the initial quote character, and increment the 
pointer.

BOOL add_quotes = FALSE;
if (memchr(start, ' ', (size_t)len) != NULL) {
add_quotes = TRUE;
cch += 2;
}

executable = (wchar_t *)malloc(cch * sizeof(wchar_t));
if (executable == NULL) {
error(RC_NO_MEMORY, L"A memory allocation failed");
}

if (add_quotes) {
*executable++ = L'\"';
}

Later, after checking the existence via GetFileAttributesW(), add the trailing 
quote and null, and decrement the pointer:

if (GetFileAttributesW(executable) == INVALID_FILE_ATTRIBUTES) {
error(RC_NO_PYTHON, L"No Python at '%ls'", executable);
}

if (add_quotes) {
size_t n = wcslen(executable);
executable[n] = L'\"';
executable[n + 1] = L'\0';
executable--;
}

--

___
Python tracker 
<https://bugs.python.org/issue46686>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46686] [venv / PC/launcher] issue with a space in the installed python path

2022-02-08 Thread Eryk Sun


Eryk Sun  added the comment:

I checked the source code in PC/launcher.c process(). It turns out that 
`executable` is not getting quoted in the venv launcher case.

CreateProcessW() tries to get around this. If the command isn't quoted, it has 
a loop that consumes up to a space (or tab) and checks for an existing file 
(not a directory). If it finds a file, it rewrites the command line to quote 
the path of the file.

My test happened to work. But it's simple enough to create an example that 
fails. For example, as an elevated admin, create a file named "C:\Program". Now 
the venv launcher won't be able to execute a base interpreter that's installed 
in "C:\Program Files":

C:\Temp>echo >C:\Program

C:\Temp>"C:\Program Files\Python310\python.exe" -m venv env
Error: Command '['C:\\Temp\\env\\Scripts\\python.exe', '-Im', 
'ensurepip', '--upgrade', '--default-pip']' returned non-zero 
exit status 101.

--
priority: normal -> critical
stage:  -> needs patch
versions: +Python 3.11

___
Python tracker 
<https://bugs.python.org/issue46686>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46686] [venv / PC/launcher] issue with a space in the installed python path

2022-02-08 Thread Eryk Sun


Eryk Sun  added the comment:

run_child() expects `cmdline` to be correctly quoted, and normally it is.

I can't reproduce this problem with Python 3.10.2. I created a user account 
with a space in the account name, logged on, and installed 3.10.2 for the 
current user, with the option enabled to add Python to PATH. Next I opened a 
command prompt in the user profile directory and created a virtual environment 
via `python.exe -m venv .venv`. Running ".venv\Scripts\python.exe -X utf8" 
worked. The command line used by the venv "python.exe" launcher was properly 
quoted and thus properly parsed in the original list of command-line arguments, 
sys.orig_argv.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46686>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46659] Deprecate locale.getdefaultlocale() function

2022-02-08 Thread Eryk Sun


Eryk Sun  added the comment:

> Oh. Serhiy asked me to use LC_TIME rather than LC_CTYPE.

Since Locale*Calendar is documented as not being thread safe, __init__() could 
get the real default via setlocale(LC_TIME, "") when locale=None and the 
current LC_TIME is "C". Restore it back to "C" after getting the default. That 
should usually match the behavior from previous versions that called 
getdefaultlocale(). In cases where it differs, it's fixing a bug because the 
default LC_TIME is the correct default.

--

___
Python tracker 
<https://bugs.python.org/issue46659>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46659] Deprecate locale.getdefaultlocale() function

2022-02-08 Thread Eryk Sun

Eryk Sun  added the comment:

> getdefaultlocale() falls back to LANG and LANGUAGE.

_Py_SetLocaleFromEnv(LC_CTYPE) (e.g. setlocale(LC_CTYPE, "")) gets called at 
startup, except for the isolated configuration [1].

I think calendar.Locale*Calendar should try the LC_CTYPE locale if LC_TIME is 
"C", i.e. (None, None). Otherwise, it's introducing new default behavior. For 
example, with LC_ALL set to "ru_RU.utf8":

3.8:

>>> locale.getlocale(locale.LC_TIME)
(None, None)
>>> locale.getlocale(locale.LC_CTYPE)
('ru_RU', 'UTF-8')
>>> cal = calendar.LocaleTextCalendar()
>>> cal.formatweekday(0, 15)
'  Понедельник  '

3.11.0a5+:

>>> locale.getlocale(locale.LC_TIME)
(None, None)
>>> locale.getlocale(locale.LC_CTYPE)
('ru_RU', 'UTF-8')
>>> cal = calendar.LocaleTextCalendar()
>>> cal.formatweekday(0, 15)
' Monday'
>>> locale.setlocale(locale.LC_TIME, '')
'ru_RU.utf8'
>>> cal = calendar.LocaleTextCalendar()
>>> cal.formatweekday(0, 15)
'  Понедельник  '

---

[1] https://docs.python.org/3/c-api/init_config.html?#isolated-configuration

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46659>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46668] encodings: the "mbcs" alias doesn't work

2022-02-07 Thread Eryk Sun


Eryk Sun  added the comment:

> I don't think that this fallback is needed anymore. Which Windows
> code page can be used as ANSI code page which is not already 
> implemented as a Python codec?

Python has full coverage of the ANSI and OEM code pages in the standard Windows 
locales, but I don't have any experience with custom (i.e. supplemental or 
replacement) locales.

https://docs.microsoft.com/en-us/windows/win32/intl/custom-locales 

Here's a simple script to check the standard locales.

import codecs
import ctypes
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

LOCALE_ALL = 0
LOCALE_WINDOWS = 1
LOCALE_IDEFAULTANSICODEPAGE = 0x1004
LOCALE_IDEFAULTCODEPAGE = 0x000B # OEM

EnumSystemLocalesEx = kernel32.EnumSystemLocalesEx
GetLocaleInfoEx = kernel32.GetLocaleInfoEx
GetCPInfoExW = kernel32.GetCPInfoExW

EnumLocalesProcEx = ctypes.WINFUNCTYPE(ctypes.c_int,
ctypes.c_wchar_p, ctypes.c_ulong, ctypes.c_void_p)

class CPINFOEXW(ctypes.Structure):
 _fields_ = (('MaxCharSize', ctypes.c_uint),
 ('DefaultChar', ctypes.c_ubyte * 2),
 ('LeadByte', ctypes.c_ubyte * 12),
 ('UnicodeDefaultChar', ctypes.c_wchar),
 ('CodePage', ctypes.c_uint),
 ('CodePageName', ctypes.c_wchar * 260))

def get_all_locale_code_pages():
result = []
seen = set()
info = (ctypes.c_wchar * 100)()

@EnumLocalesProcEx
def callback(locale, flags, param):
for lctype in (LOCALE_IDEFAULTANSICODEPAGE, 
LOCALE_IDEFAULTCODEPAGE):
if (GetLocaleInfoEx(locale, lctype, info, len(info)) and
  info.value not in ('0', '1')):
cp = int(info.value)
if cp in seen:
continue
seen.add(cp)
cp_info = CPINFOEXW()
if not GetCPInfoExW(cp, 0, ctypes.byref(cp_info)):
cp_info.CodePage = cp
cp_info.CodePageName = str(cp)
result.append(cp_info)
return True

if not EnumSystemLocalesEx(callback, LOCALE_WINDOWS, None, None):
raise ctypes.WinError(ctypes.get_last_error())

result.sort(key=lambda x: x.CodePage)
return result

supported = []
unsupported = []
for cp_info in get_all_locale_code_pages():
cp = cp_info.CodePage
try:
codecs.lookup(f'cp{cp}')
except LookupError:
unsupported.append(cp_info)
else:
supported.append(cp_info)

if unsupported:
print('Unsupported:\n')
for cp_info in unsupported:
print(cp_info.CodePageName)
print('\nSupported:\n')
else:
print('All Supported:\n')
for cp_info in supported:
print(cp_info.CodePageName)


Output:

All Supported:

437   (OEM - United States)
720   (Arabic - Transparent ASMO)
737   (OEM - Greek 437G)
775   (OEM - Baltic)
850   (OEM - Multilingual Latin I)
852   (OEM - Latin II)
855   (OEM - Cyrillic)
857   (OEM - Turkish)
862   (OEM - Hebrew)
866   (OEM - Russian)
874   (ANSI/OEM - Thai)
932   (ANSI/OEM - Japanese Shift-JIS)
936   (ANSI/OEM - Simplified Chinese GBK)
949   (ANSI/OEM - Korean)
950   (ANSI/OEM - Traditional Chinese Big5)
1250  (ANSI - Central Europe)
1251  (ANSI - Cyrillic)
1252  (ANSI - Latin I)
1253  (ANSI - Greek)
1254  (ANSI - Turkish)
1255  (ANSI - Hebrew)
1256  (ANSI - Arabic)
1257  (ANSI - Baltic)
1258  (ANSI/OEM - Viet Nam)

Some locales are Unicode only (e.g. Hindi-India) or have no OEM code page, 
which the above code skips by checking for "0" or "1" as the code page value. 
Windows 10+ allows setting the system locale to a Unicode-only locale, for 
which it uses UTF-8 (65001) for ANSI and OEM.

The OEM code page matters because the console input and output code pages 
default to OEM, e.g. for os.device_encoding(). The console's I/O code pages are 
used in Python by low-level os.read() and os.write(). Note that the console 
doesn't properly implement using UTF-8 (65001) as the input code page. In this 
case, input read from the console via ReadFile() or ReadConsoleA() has a null 
byte in place of each non-ASCII character.

--

___
Python tracker 
<https://bugs.python.org/issue46668>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46668] encodings: the "mbcs" alias doesn't work

2022-02-06 Thread Eryk Sun


Eryk Sun  added the comment:

> The Python 3.6 and 3.7 "codecs.register(_alias_mbcs)" doesn't work 
> because "search_function()" is tested before and it works for "cpXXX" 
> encodings.

Isn't the 3.6-3.10 ordering of search_function() and _alias_mbcs() correct as a 
fallback? In this case, Python doesn't support a cross-platform encoding for 
the code page. That's why the old implementation of test_mbcs_alias() mocked 
_winapi.GetACP() to return 123 and then checked that looking up 'cp123' 
returned the "mbcs" codec.

I'd actually prefer to extend this by implementing _winapi.GetOEMCP() and using 
"oem" as a fallback for that case.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46668>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46654] urllib.request.urlopen doesn't handle UNC paths produced by pathlib's as_uri() (but can handle UNC paths with additional slashes)

2022-02-06 Thread Eryk Sun


Change by Eryk Sun :


--
assignee: docs@python -> 
components:  -2to3 (2.x to 3.x conversion tool), Argument Clinic, Build, C API, 
Cross-Build, Demos and Tools, Distutils, Documentation, Extension Modules, 
FreeBSD, IDLE, IO, Installation, Interpreter Core, Parser, Regular Expressions, 
SSL, Subinterpreters, Tests, Tkinter, Unicode, Windows, XML, asyncio, ctypes, 
email, macOS
stage:  -> needs patch
type: performance -> behavior
versions:  -Python 3.7, Python 3.8

___
Python tracker 
<https://bugs.python.org/issue46654>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46654] urllib.request.urlopen doesn't handle UNC paths produced by pathlib's as_uri() (but can handle UNC paths with additional slashes)

2022-02-06 Thread Eryk Sun


Eryk Sun  added the comment:

> The value of req.selector never starts with "//", for which file_open() 
> checks, but rather a single slash, such as "/Z:/test.py" or 
> "/share/test.py".

To correct myself, actually req.selector will start with "//" for a "file:" 
URI, such as "file:host/share/test.py". For this example, req.host is an 
empty string, so file_open() still ends up calling open_local_file(), which 
will open "//host/share/test.py". In Linux, "//host/share" is the same as 
"/host/share". In Cygwin and MSYS2 it's a UNC path. I guess this case should be 
allowed, even though the meaning of a "//" root isn't specifically defined in 
POSIX.

Unless I'm overlooking something, file_open() only has to check the value of 
req.host. In POSIX, it should require opening a 'local' path, i.e. if req.host 
isn't None, empty, or a local host, raise URLError.

In Windows, my tests show that the shell API special cases "localhost" (case 
insensitive) in "file:" URIs. For example, the following are all equivalent: 
"file:/C:/Temp", "file:///C:/Temp", and "file://localhost/C:/Temp". The shell 
API does not special case the real local host name or any of its IP addresses, 
such as 127.0.0.1. They're all handled as UNC paths.

Here's what I've experimented with thus far, which passes the existing urllib 
tests in Linux and Windows:

class FileHandler(BaseHandler):
def file_open(self, req):
if not self._is_local_path(req):
if sys.platform == 'win32':
path = url2pathname(f'//{req.host}{req.selector}')
else:
raise URLError("In POSIX, the file:// scheme is only "
   "supported for local file paths.")
else:
path = url2pathname(req.selector)
return self._common_open_file(req, path)


def _is_local_path(self, req):
if req.host:
host, port = _splitport(req.host)
if port:
raise URLError(f"the host cannot have a port: {req.host}")
if host.lower() != 'localhost':
# In Windows, all other host names are UNC.
if sys.platform == 'win32':
return False
# In POSIX, support all names for the local host.
if _safe_gethostbyname(host) not in self.get_names():
return False
return True


# names for the localhost
names = None
def get_names(self):
if FileHandler.names is None:
try:
FileHandler.names = tuple(
socket.gethostbyname_ex('localhost')[2] +
socket.gethostbyname_ex(socket.gethostname())[2])
except socket.gaierror:
FileHandler.names = (socket.gethostbyname('localhost'),)
return FileHandler.names


def open_local_file(self, req):
if not self._is_local_path(req):
raise URLError('file not on local host')
return self._common_open_file(req, url2pathname(req.selector))


def _common_open_file(self, req, path):
import email.utils
import mimetypes
host = req.host
filename = req.selector
try:
if host:
origurl = f'file://{host}{filename}'
else:
origurl = f'file://{filename}'
stats = os.stat(path)
size = stats.st_size
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
mtype = mimetypes.guess_type(filename)[0] or 'text/plain'
headers = email.message_from_string(
f'Content-type: {mtype}\n'
f'Content-length: {size}\n'
f'Last-modified: {modified}\n')
return addinfourl(open(path, 'rb'), headers, origurl)
except OSError as exp:
raise URLError(exp)


Unfortunately nturl2path.url2pathname() parses some UNC paths incorrectly. For 
example, the following path should be an invalid UNC path, since "C:" is an 
invalid name, but instead it gets converted into an unrelated local path.

>>> nturl2path.url2pathname('//host/C:/Temp/spam.txt')
'C:\\Temp\\spam.txt'

This goof depends on finding ":" or "|" in the path. It's arguably worse if the 
last component has a named data stream (allowed by RFC 8089):

>>> nturl2path.url2pathname('//host/share/spam.txt:eggs')
'T:\\eggs'

Drive "T:" is from "t:" in "t:eggs", 

[issue46654] urllib.request.urlopen doesn't handle UNC paths produced by pathlib's resolve() (but can handle UNC paths with additional slashes)

2022-02-05 Thread Eryk Sun


Eryk Sun  added the comment:

> file://server/host/file.ext on windows, even though 
> file:server/host/file.ext open just fine.

For r"\\host\share\test.py", the two slash conversion 
"file://host/share/test.py" is correct according to RFC80889 "E.3.1.  URI 
with Authority" [1]. In this case, req.host is "host", and req.selector is 
"/share/test.py". 

The four slash version "file:host/share/test.py" is a known variant for a 
converted UNC path, as noted in RFC8089 "E.3.2.  URI with UNC Path". In 
this case, req.host is an empty string, and req.selector is 
"//host/share/test.py". There's another variant that uses 5 slashes for a UNC 
path, but urllib (or url2pathname) doesn't support it.

---
[1] https://datatracker.ietf.org/doc/html/rfc8089

--

___
Python tracker 
<https://bugs.python.org/issue46654>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46654] urllib.request.urlopen doesn't handle UNC paths produced by pathlib's resolve() (but can handle UNC paths with additional slashes)

2022-02-05 Thread Eryk Sun


Eryk Sun  added the comment:

In FileHandler.file_open(), req.host is the host name, which is either None or 
an empty string for a local drive path such as, respectively, 
"file:/Z:/test.py" or "file:///Z:/test.py". The value of req.selector never 
starts with "//", for which file_open() checks, but rather a single slash, such 
as "/Z:/test.py" or "/share/test.py". This is a bug in file_open(). Due to this 
bug, it always calls self.open_local_file(req), even if req.host isn't local. 
The distinction shouldn't matter in Windows, which supports UNC paths, but 
POSIX has to open a path on the local machine (possibly a mount point for a 
remote path, but that's irrelevant). In POSIX, if the local machine 
coincidentally has the req.selector path, then the os.stat() and open() calls 
will succeed with a bogus result.

For "file://host/share/test.py", req.selector is "/share/test.py". In Windows, 
url2pathname() converts this to r"\share\test.py", which is relative to the 
drive of the process current working directory. This is a bug in 
open_local_file() on Windows. For it to work correctly, req.host has to be 
joined back with req.selector as the UNC path "//host/share/test.py". Of 
course, this need not be a local file in Windows, so Windows should be exempted 
from the local file limitation in file_open().

--

___
Python tracker 
<https://bugs.python.org/issue46654>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46654] file_open doesn't handle UNC paths produced by pathlib's resolve() (but can handle UNC paths with additional slashes)

2022-02-05 Thread Eryk Sun


Change by Eryk Sun :


--
Removed message: https://bugs.python.org/msg412604

___
Python tracker 
<https://bugs.python.org/issue46654>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46654] file_open doesn't handle UNC paths produced by pathlib's resolve() (but can handle UNC paths with additional slashes)

2022-02-05 Thread Eryk Sun


Eryk Sun  added the comment:

Builtin open() calls C open(). This C function supports whatever path types are 
supported natively. In Windows, C open() calls WinAPI CreateFileW(), which does 
not support "file://" URIs. The Windows API handles it as a relative path, 
which gets resolved against the current working directory. For example:

>>> os.getcwd()
'C:\\Temp'
>>> nt._getfullpathname('file:host/share/file')
'C:\\Temp\\file:\\host\\share\\file'
>>> nt._getfullpathname('file://host/share/file')
'C:\\Temp\\file:\\host\\share\\file'

As to the resolved path somehow working, that generally will not be the case. 
Most filesystems in Windows will reject a path component named "file:" as an 
invalid name. The ":" character is usually disallowed in base file and 
directory names, since some Windows filesystems use it as a delimiter in file 
streams, e.g. "name:stream_name:stream_type". The default data stream in a 
regular file has no stream name, and its stream type is "$DATA". Thus for base 
name "file", the default data stream can be referenced explicitly as 
"file::$DATA". But just "file:", with neither a stream name nor a stream type, 
is an invalid name.

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46654>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46631] Implement a "strict" mode for getpass.getuser()

2022-02-04 Thread Eryk Sun


Eryk Sun  added the comment:

Here's an example for the suggested changes to _winapi.

Include these headers:

#include  // LsaGetLogonSessionData
#include  // STATUS_SUCCESS

Add these argument-clinic macros to _winapi_functions:

_WINAPI_GETCURRENTPROCESSTOKEN_METHODDEF
_WINAPI_GETTOKENINFORMATION_METHODDEF
_WINAPI_LSAGETLOGONSESSIONDATA_METHODDEF

Add TokenStatistics in winapi_exec():

WINAPI_CONSTANT(F_DWORD, TokenStatistics);

Add minimal implementations that wrap the WinAPI functions:

/*[clinic input]
_winapi.GetCurrentProcessToken -> HANDLE

Return a handle for the access token of the current process.
[clinic start generated code]*/

static HANDLE
_winapi_GetCurrentProcessToken_impl(PyObject *module)
/*[clinic end generated code: output=cf8e8e20dd41dd6e 
input=73a282cf3718af9e]*/

{
return GetCurrentProcessToken();
}


/*[clinic input]
_winapi.GetTokenInformation

handle: HANDLE
information_class: unsigned_long
/

Get information from an access token.
[clinic start generated code]*/

static PyObject *
_winapi_GetTokenInformation_impl(PyObject *module, HANDLE handle,
 unsigned long information_class)
/*[clinic end generated code: output=caecec0a25658348 
input=b277ad2414f1b03e]*/

{
if (information_class != TokenStatistics) {
return PyErr_Format(
PyExc_NotImplementedError,
"Unsupported information class: %d",
information_class);
}

DWORD returned_size;
TOKEN_STATISTICS info;

if (!GetTokenInformation(handle, information_class, , sizeof(info),
_size)) {
return PyErr_SetFromWindowsErr(0);
}

PyObject *result = PyDict_New();
if (!result) {
return NULL;
}

PyObject *value = PyLong_FromUnsignedLongLong(
(((uint64_t)info.AuthenticationId.HighPart) << 32) +
info.AuthenticationId.LowPart);
if (!value) {
goto error;
}
if (PyDict_SetItemString(result, "AuthenticationId", value) < 0) {
Py_DECREF(value);
goto error;
}
Py_DECREF(value);

return result;

error:
Py_CLEAR(result);
return NULL;
}


/*[clinic input]
_winapi.LsaGetLogonSessionData

logon_id: unsigned_long_long
/
Get data for the logon session identified by logon_id.
[clinic start generated code]*/

static PyObject *
_winapi_LsaGetLogonSessionData_impl(PyObject *module,
unsigned long long logon_id)
/*[clinic end generated code: output=680ac7725ef34527 
input=01ff4216b89d01ef]*/

{
SECURITY_LOGON_SESSION_DATA *pdata;
LUID logon_luid;
logon_luid.HighPart = logon_id >> 32;
logon_luid.LowPart = logon_id & 0x;

NTSTATUS status = LsaGetLogonSessionData(_luid, );
if (status != STATUS_SUCCESS) {
return PyErr_SetFromWindowsErr(LsaNtStatusToWinError(status));
}

PyObject *result = PyDict_New();
if (!result) {
goto error;
}

PyObject *value = PyUnicode_FromWideChar(pdata->UserName.Buffer,
pdata->UserName.Length / sizeof(WCHAR));
if (!value) {
goto error;
}
if (PyDict_SetItemString(result, "UserName", value) < 0) {
Py_DECREF(value);
goto error;
}
Py_DECREF(value);

LsaFreeReturnBuffer(pdata);
return result;

error:
LsaFreeReturnBuffer(pdata);
Py_CLEAR(result);
return NULL;
}

--

___
Python tracker 
<https://bugs.python.org/issue46631>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46631] Implement a "strict" mode for getpass.getuser()

2022-02-03 Thread Eryk Sun


New submission from Eryk Sun :

getpass.getuser() checks the environment variables LOGNAME (login name), USER, 
LNAME, and USERNAME, in that order. In Windows, LOGNAME, USER, and LNAME have 
no conventional usage. I think there should be a strict mode that restricts 
getuser() to check only USERNAME in Windows and only LOGNAME in POSIX [1]. If 
the login variable isn't defined, it should fall back on using the system API, 
based on the user ID in POSIX and the logon ID in Windows.

For the fallback in Windows, the _winapi module could implement 
GetCurrentProcessToken(), GetTokenInformation(), and LsaGetLogonSessionData(). 
For TokenStatistics, return a dict with just "AuthenticationId". For 
LsaGetLogonSessionData(), return a dict with just "UserName". 
GetCurrentProcessToken() returns a pseudohandle (-4), which should not be 
closed.

For example, assuming _winapi wraps the required functions:

def getuser(strict=False):
"""Get the username from the environment or password database.

First try various environment variables. If strict, check only LOGNAME
in POSIX and only USERNAME in Windows. As a fallback, in POSIX get the
user name from the password database, and in Windows get the user name
from the logon-session data of the current process.
"""
posix = sys.platform != 'win32'

if strict:
names = ('LOGNAME',) if posix else ('USERNAME',)
else:
names = ('LOGNAME', 'USER', 'LNAME', 'USERNAME')

for name in names:
if user := os.environ.get(name):
return user

if posix:
import pwd
return pwd.getpwuid(os.getuid())[0]

import _winapi
logon_id = _winapi.GetTokenInformation(
_winapi.GetCurrentProcessToken(),
_winapi.TokenStatistics)['AuthenticationId']
return _winapi.LsaGetLogonSessionData(logon_id)['UserName']

Like WinAPI GetUserNameW(), the above fallback returns the logon user name 
instead of the account name of the token user. As far as I know, the user name 
and the account name only differ for the builtin service account logons 
"SYSTEM" (999) and "NETWORK SERVICE" (996), for which the user name is the 
machine security principal (i.e. the machine's NETBIOS name plus "$"). The user 
name of the builtin "LOCAL SERVICE" logon (997), on the other hand, is just the 
"LOCAL SERVICE" account name, since this account lacks network access.

Unlike GetUserNameW(), the above code uses the process token instead of the 
effective token. This is like POSIX getuid(), whereas what GetUserNameW() does 
is like geteuid(). getuser() could implement an `effective` option to return 
the effective user name. In Windows this would switch to calling 
GetCurrentThreadEffectiveToken() instead of GetCurrentProcessToken().

---

[1] https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html

--
components: Library (Lib), Windows
messages: 412495
nosy: eryksun, paul.moore, steve.dower, tim.golden, zach.ware
priority: normal
severity: normal
stage: needs patch
status: open
title: Implement a "strict" mode for getpass.getuser()
type: enhancement
versions: Python 3.11

___
Python tracker 
<https://bugs.python.org/issue46631>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46490] Add "follow_symlinks=False" support for "os.utime()" on Windows

2022-02-02 Thread Eryk Sun


Eryk Sun  added the comment:

In case you missed it, I implemented _Py_CreateFile2() in bpo-46506 and rewrote 
os.stat() based on it. Check it out in case you're interested in moving forward 
with a PR in bpo-46506.

For this issue, follow_symlinks is fairly simple to support with 
_Py_CreateFile2(). We may as well add fd support, since that's trivial to add. 
For example:

if (path->fd != -1) {
hFile = _Py_get_osfhandle(path->fd);
} else {
Py_BEGIN_ALLOW_THREADS
hFile = _Py_CreateFile2(path->wide, FILE_WRITE_ATTRIBUTES, 0,
OPEN_EXISTING, NULL, follow_symlinks, NULL);
Py_END_ALLOW_THREADS
}
if (hFile == INVALID_HANDLE_VALUE) {
if (path->fd == -1) {
path_error(path);
}
return NULL;
}

One also has to define the following macros to declare follow_symlinks and fd 
support: UTIME_HAVE_NOFOLLOW_SYMLINKS and PATH_UTIME_HAVE_FD.

To announce support in os.supports_follow_symlinks and os.supports_fd, it 
should be conditioned on MS_WINDOWS, i.e. _add("MS_WINDOWS", "utime"). The os 
module is frozen, so changing these two sets requires rebuilding Python.

--

___
Python tracker 
<https://bugs.python.org/issue46490>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46594] Windows "Edit with IDLE >" only has one selection

2022-02-01 Thread Eryk Sun


Eryk Sun  added the comment:

> the Open With entries may not be being merged.

That would probably be a bug in the Windows shell API. The HKCU and HKLM 
subkeys of "Software\Classes\Python.File\Shell\editwithidle\shell" are merged 
in the HKCR view. The same key path can exist in both hives, for which the view 
contains the union, with precedence for HKCU.

For example, with "Software\Classes\spam":

import winreg

hkm = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, r'Software\Classes\spam')
winreg.SetValueEx(hkm, 'eggs', 0, winreg.REG_SZ, 'hklm')
winreg.SetValueEx(hkm, 'baz', 0, winreg.REG_SZ, 'hklm')

hku = winreg.CreateKey(winreg.HKEY_CURRENT_USER, r'Software\Classes\spam')
winreg.SetValueEx(hku, 'eggs', 0, winreg.REG_SZ, 'hkcu')
winreg.SetValueEx(hku, 'bam', 0, winreg.REG_SZ, 'hkcu')

hkr = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'spam')

>>> winreg.EnumValue(hkr, 0)
('bam', 'hkcu', 1)
>>> winreg.EnumValue(hkr, 1)
('baz', 'hklm', 1)
>>> winreg.EnumValue(hkr, 2)
('eggs', 'hkcu', 1)

>>> winreg.EnumValue(hkr, 3)
Traceback (most recent call last):
  File "", line 1, in 
OSError: [WinError 259] No more data is available

--

___
Python tracker 
<https://bugs.python.org/issue46594>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue46594] Windows "Edit with IDLE >" only has one selection

2022-01-31 Thread Eryk Sun


Eryk Sun  added the comment:

Check your settings in the registry.

In "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts" there 
should be a ".py" key, but not necessarily. It should have an "OpenWithList" 
subkey that contains an "MRUList" value (most recently used list). The value 
should be a sequence of letters, each of which should be a value name in the 
key. If the launcher was last used to open a ".py" file, the first letter in 
the list should be a value with the data "py.exe".

Ideally there should also be a subkey named "UserChoice" that contains a 
"ProgId" (programmatic identifier) value with the data "Python.File". This sets 
the "Python.File" ProgID as the locked-in user choice for ".py" files. In the 
GUI, you can set this in the open-with dialog by selecting "always use this app 
to open .py files". The selected app should be "Python", with an icon that 
contains the Python logo and a rocket (the launcher).

If ".py" isn't the locked-in user choice, the shell API will use the most 
recent user selection in the open-with menu. If there's no user selection, the 
default association is calculated from "HKCR\.py", which is a merged view of 
"[HKCU|HKLM]\Software\Classes\.py". The default value of "HKCR\.py" sets the 
default file association. Ideally it should be "Python.File".

"HKCR\Python.File" is a merged view of 
"[HKCU|HKLM]\Software\Classes\Python.File". For the merged view, if a value 
name is defined in the same subkey of HKCU and HKLM, the view prefers the HKCU 
value. There should be a subkey named "shell\editwithidle\shell". It should 
define one or more subkeys named "edit3*", such as "edit310". Each should 
contain a "MUIVerb" value that sets the command description in the "open-with" 
menu. There should also be a "command" subkey that contains the template 
command as its default value, e.g. ""C:\Program Files\Python310\pythonw.exe" -m 
idlelib "%L" %*".

--
nosy: +eryksun

___
Python tracker 
<https://bugs.python.org/issue46594>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue29688] Add support for Path.absolute()

2022-01-31 Thread Eryk Sun


Eryk Sun  added the comment:

> I'd imagine that bug is reproducible with `Path('C:\\Temp', 'C:')` 
> already, right? If that's the case, should it logged as a 
> separate issue?

Yes, it's a separate issue that affects the _from_parts() call in absolute().

How about designing absolute() to create a new instance from an absolute path 
that's created by os.path? For example: 

join(abspath(self.drive) if self.drive else getcwd(), self)

Of course use accessor functions.

--

___
Python tracker 
<https://bugs.python.org/issue29688>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue29688] Add support for Path.absolute()

2022-01-31 Thread Eryk Sun


Eryk Sun  added the comment:

> I'm not seeing what's wrong with your example.

"C:" or "C:spam\\eggs" are not absolute paths. They depend on the effective 
working directory on the drive. An absolute path should never depend on a 
working directory, which can change at random.

WinAPI SetEnvironmentVariableW() allows applications to set environment 
variables with names that begin with "=". These names are effectively reserved 
for special use by the OS, at least as documented. In particular, names of the 
form "=X:", where "X" is a drive letter, are used to store the working 
directory on a drive. The C runtime _[w]chdir() function sets these per-drive 
environment variables, as does Python's os.chdir(). As environment variables, 
they can be inherited by child processes.

When then Windows API resolves a file path to access a file, or in 
GetFullPathNameW(), a drive-relative path such as "X:" or "X:spam\\eggs" is 
resolved against either the current working directory (if it's on the drive) or 
the value of the "=X:" environment variable for the drive. If the latter isn't 
defined, it defaults to the root directory, e.g. "X:\\". If the current working 
directory is on the drive, the system updates the value of the "=X:" 
environment variable, if it exists.

> on Windows you have to resolve the drive separately from the 
> working directory and then concatenate them?

No, if self.drive is defined, then abspath(self.drive) should be called instead 
of getcwd(). In Windows, ntpath.abspath() calls WinAPI GetFullPathNameW(), 
which resolves the working directory on the drive.

--

___
Python tracker 
<https://bugs.python.org/issue29688>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue29688] Add support for Path.absolute()

2022-01-31 Thread Eryk Sun


Change by Eryk Sun :


--
Removed message: https://bugs.python.org/msg412220

___
Python tracker 
<https://bugs.python.org/issue29688>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



[issue29688] Add support for Path.absolute()

2022-01-31 Thread Eryk Sun


Eryk Sun  added the comment:

> I'm not seeing what's wrong with your example.

"C:" or "C:spam\\eggs" are not absolute paths. They depend on the effective 
working directory on the drive. An absolute path should never depend on a 
working directory, which can change at random.

WinAPI SetEnvironmentVariableW() allows applications to set environment 
variables with names that begin with "=". These names are effectively reserved 
for special use by the OS, at least as documented. In particular, names of the 
form "=X:", where "X" is a drive letter, are used to store the working 
directory on a drive. The C runtime _[w]chdir() function sets these per-drive 
environment variables, as does Python's os.chdir(). As environment variables, 
they can be inherited by child processes.

When then Windows API resolves a file path to access a file, or in 
GetFullPathNameW(), a drive-relative path such as "X:" or "X:spam\\eggs" is 
resolved against either the current working directory (if it's on the drive) or 
the value of the "=X:" environment variable for the drive. If the latter isn't 
defined, it defaults to the root directory, e.g. "X:\\". If the current working 
directory is on the drive, the system updates the value of the "=X:" 
environment variable, if it exists.

> on Windows you have to resolve the drive separately from the 
> working directory and then concatenate them?

Yes, if self.drive is defined. This would be handled by 
os.path.abspath(self.drive), which calls WinAPI GetFullPathNameW().

--

___
Python tracker 
<https://bugs.python.org/issue29688>
___
___
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com



  1   2   3   4   5   6   7   8   9   10   >