Repository: pyinstaller/pyinstaller

Issue:
[retry mechanism(?)]: "The system cannot open the device or file specified."
## Description of the issue

I cannot reproduce the issue, but I was getting the stacktrace below, and I believe that is a case caused by AntiVirus software but not (sufficiently) handled by the existing retry mechanism.

### Context information (for bug reports)

* Output of `pyinstaller --version`: ```6.2.0```
* Version of Python: 3.11.6
* Platform: Windows 10 22H2
* How you installed Python: scoop + virtualenv
* Did you also try this on another platform? Does it work there? No

### Stacktrace / full error message

```
Traceback (most recent call last):
  File "c:\Build\project\.venv\Lib\site-packages\win32ctypes\pywin32\pywintypes.py", line 33, in pywin32error
    yield
  File "c:\Build\project\.venv\Lib\site-packages\win32ctypes\pywin32\win32api.py", line 226, in EndUpdateResource
    _resource._EndUpdateResource(handle, discard)
  File "c:\Build\project\.venv\Lib\site-packages\win32ctypes\core\ctypes\_util.py", line 61, in check_false
    raise make_error(function, function_name)
OSError: [WinError 110] The system cannot open the device or file specified.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "c:\Build\project\.venv\Scripts\pyinstaller.exe\__main__.py", line 7, in <module>
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\__main__.py", line 214, in _console_script_run
    run()
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\__main__.py", line 198, in run
    run_build(pyi_config, spec_file, **vars(args))
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\__main__.py", line 69, in run_build
    PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\building\build_main.py", line 1069, in main
    build(specfile, distpath, workpath, clean_build)
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\building\build_main.py", line 1009, in build
    exec(code, spec_namespace)
  File "project.spec", line 52, in <module>
    exe = EXE(
          ^^^^
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\building\api.py", line 628, in __init__
    self.__postinit__()
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\building\datastruct.py", line 184, in __postinit__
    self.assemble()
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\building\api.py", line 737, in assemble
    self._retry_operation(winresource.remove_all_resources, build_name)
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\building\api.py", line 995, in _retry_operation
    return func(*args)
           ^^^^^^^^^^^
  File "c:\Build\project\.venv\Lib\site-packages\PyInstaller\utils\win32\winresource.py", line 189, in remove_all_resources
    win32api.EndUpdateResource(module_handle, False)
  File "c:\Build\project\.venv\Lib\site-packages\win32ctypes\pywin32\win32api.py", line 225, in EndUpdateResource
    with _pywin32error():
  File "c:\Scoop\apps\python311\current\Lib\contextlib.py", line 155, in __exit__
    self.gen.throw(typ, value, traceback)
  File "c:\Build\project\.venv\Lib\site-packages\win32ctypes\pywin32\pywintypes.py", line 37, in pywin32error
    raise error(exception.winerror, exception.function, exception.strerror)
win32ctypes.pywin32.pywintypes.error: (110, 'EndUpdateResourceW', 'The system cannot open the device or file specified.')
```



Discussion:

Resolving patch:
diff --git a/PyInstaller/building/api.py b/PyInstaller/building/api.py
--- a/PyInstaller/building/api.py
+++ b/PyInstaller/building/api.py
@@ -965,28 +965,36 @@ def _is_allowed_exception(e):
             """
             Helper to determine whether the given exception is eligible for retry or not.
             """
-            if is_win:
-                from PyInstaller.compat import pywintypes
-
             if isinstance(e, PermissionError):
                 # Always retry on all instances of PermissionError
                 return True
-            elif is_win and isinstance(e, OSError):
-                # For other types of OSError, validate errno (the values below are specific to Windows)
+            elif is_win:
+                from PyInstaller.compat import pywintypes
+
+                # Windows-specific errno and winerror codes.
+                # https://learn.microsoft.com/en-us/cpp/c-runtime-library/errno-constants
                 _ALLOWED_ERRNO = {
                     13,  # EACCES (would typically be a PermissionError instead)
                     22,  # EINVAL (reported to be caused by Crowdstrike; see #7840)
                 }
-                if e.errno in _ALLOWED_ERRNO:
-                    return True
-            elif is_win and isinstance(e, pywintypes.error):
-                # pywintypes.error is raised by helper functions that use win32 C API bound via pywin32-ctypes.
+                # https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
                 _ALLOWED_WINERROR = {
                     5,  # ERROR_ACCESS_DENIED (reported in #7825)
                     32,  # ERROR_SHARING_VIOLATION (exclusive lock via `CreateFileW` flags, or via `_locked`).
+                    110,  # ERROR_OPEN_FAILED (reported in #8138)
                 }
-                if e.winerror in _ALLOWED_WINERROR:
-                    return True
+                if isinstance(e, OSError):
+                    # For OSError exceptions other than PermissionError, validate errno.
+                    if e.errno in _ALLOWED_ERRNO:
+                        return True
+                    # OSError typically translates `winerror` into `errno` equivalent; but try to match the original
+                    # values as a fall back, just in case. `OSError.winerror` attribute exists only on Windows.
+                    if e.winerror in _ALLOWED_WINERROR:
+                        return True
+                elif isinstance(e, pywintypes.error):
+                    # pywintypes.error is raised by helper functions that use win32 C API bound via pywin32-ctypes.
+                    if e.winerror in _ALLOWED_WINERROR:
+                        return True
             return False
 
         func_name = func.__name__


Test patch: