Repository: miurahr/aqtinstall

Issue:
Installation of 6.4.0 win64_msvc2019_64 fails on azure devops pipeline runner on windows_2019 image
**Describe the bug**
when I execute
     ` aqt install-qt --outputdir $(Build.BinariesDirectory)\\Qt windows desktop 6.4.0 win64_msvc2019_64 -m all `
the script aborts with disk full exception
But this disk D drive has 12GByte free space before
windows-latest or windows-2022 image do not have  MSVC2019 whereas windows-2019 image has.
So I choose windows-2019 image

**To Reproduce**
Steps to reproduce the behavior:
  # 1
```
  - script: |
      cd $(Build.BinariesDirectory)
      python -m pip install aqtinstall
      echo "Installing Qt to $(Build.BinariesDirectory)"
      echo "install qt 6.4.0"
      aqt install-qt --outputdir $(Build.BinariesDirectory)\\Qt windows desktop 6.4.0 win64_msvc2019_64 -m all

```
**Expected behavior**
A clear and concise description of what you expected to happen.

**`aqt` output**
Add program output to help explain your problem.

```
INFO    : aqtinstall(aqt) v3.0.1 on Python 3.10.7 [CPython MSC v.1933 64 bit (AMD64)]
INFO    : Downloading qt3d...
INFO    : Downloading qthttpserver...
INFO    : Downloading qtnetworkauth...
INFO    : Downloading qtquicktimeline...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qtquicktimeline-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 1.48560150
INFO    : Downloading qtshadertools...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qtshadertools-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 1.48801890
INFO    : Downloading qtbase...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qtdeclarative-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64-debug-symbols.7z in 44.93251080
INFO    : Downloading qttools...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qtdeclarative-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 35.35354360
INFO    : Downloading qttools...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qttools-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64-debug-symbols.7z in 14.13964240
INFO    : Downloading qt5compat...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qt5compat-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64-debug-symbols.7z in 2.23388360
INFO    : Finished installation of qtbase-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 24.40250470
INFO    : Downloading qtsvg...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qtsvg-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 1.05774990
INFO    : Finished installation of qttools-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 8.52346590
INFO    : Downloading qttranslations...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of qttranslations-Windows-Windows_10_21H2-MSVC2019-Windows-Windows_10_21H2-X86_64.7z in 1.42207410
INFO    : Downloading d3dcompiler_47...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of d3dcompiler_47-x64.7z in 1.22245150
INFO    : Downloading opengl32sw...
INFO    : Redirected: qt.mirror.constant.com
INFO    : Finished installation of opengl32sw-64-mesa_11_2_2-signed.7z in 1.74708810
WARNING : Caught OSError, terminating installer workers
ERROR   : [Errno 28] No space left on device
Traceback (most recent call last):
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\site-packages\aqt\installer.py", line 108, in run
    args.func(args)
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\site-packages\aqt\installer.py", line 348, in run_install_qt
    run_installer(qt_archives.get_packages(), base_dir, sevenzip, keep, _archive_dest)
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\site-packages\aqt\installer.py", line 1055, in run_installer
    raise e from e
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\site-packages\aqt\installer.py", line 1031, in run_installer
    pool.starmap(installer, tasks)
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\multiprocessing\pool.py", line 375, in starmap
    return self._map_async(func, iterable, starmapstar, chunksize).get()
  File "C:\hostedtoolcache\windows\Python\3.10.7\x64\lib\multiprocessing\pool.py", line 774, in get
    raise self._value
OSError: [Errno 28] No space left on device
ERROR   : aqtinstall(aqt) v3.0.1 on Python 3.10.7 [CPython MSC v.1933 64 bit (AMD64)]
Working dir: `D:\a\1\b`
Arguments: `['C:\\hostedtoolcache\\windows\\Python\\3.10.7\\x64\\Scripts\\aqt', 'install-qt', '--outputdir', 'D:\\a\\1\\b\\\\Qt', 'windows', 'desktop', '6.4.0', 'win64_msvc2019_64', '-m', 'all']` Host: `uname_result(system='Windows', node='fv-az182-616', release='10', version='10.0.17763', machine='AMD64')`
===========================PLEASE FILE A BUG REPORT===========================
You have discovered a bug in aqt.
Please file a bug report at https://github.com/miurahr/aqtinstall/issues.
Please remember to include a copy of this program's output in your report.
##[error]Cmd.exe exited with code '254'.

```

**Desktop (please complete the following information):**
 - windows-2019 image https://github.com/actions/runner-images/blob/main/images/win/Windows2019-Readme.md
 - `aqt` version [paste the output from the command `aqt version` or `python -m aqt version`]

**Additional context**
Add any other context about the problem here.


Discussion:

Patch:
diff --git a/aqt/exceptions.py b/aqt/exceptions.py
index 012a8c8..972488c 100644
--- a/aqt/exceptions.py
+++ b/aqt/exceptions.py
@@ -102,3 +102,11 @@ class UpdaterError(AqtException):
 
 class OutOfMemory(AqtException):
     pass
+
+
+class OutOfDiskSpace(AqtException):
+    pass
+
+
+class DiskAccessNotPermitted(AqtException):
+    pass
diff --git a/aqt/installer.py b/aqt/installer.py
index 4af89c8..a27fd2e 100644
--- a/aqt/installer.py
+++ b/aqt/installer.py
@@ -22,6 +22,7 @@
 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 import argparse
+import errno
 import gc
 import multiprocessing
 import os
@@ -47,6 +48,8 @@ from aqt.exceptions import (
     ArchiveListError,
     CliInputError,
     CliKeyboardInterrupt,
+    DiskAccessNotPermitted,
+    OutOfDiskSpace,
     OutOfMemory,
 )
 from aqt.helper import (
@@ -1127,6 +1130,23 @@ def run_installer(archives: List[QtPackage], base_dir: str, sevenzip: Optional[s
         pool.starmap(installer, tasks)
         pool.close()
         pool.join()
+    except PermissionError as e:  # subclass of OSError
+        close_worker_pool_on_exception(e)
+        raise DiskAccessNotPermitted(
+            f"Failed to write to base directory at {base_dir}",
+            suggested_action=[
+                "Check that the destination is writable and does not already contain files owned by another user."
+            ],
+        ) from e
+    except OSError as e:
+        close_worker_pool_on_exception(e)
+        if e.errno == errno.ENOSPC:
+            raise OutOfDiskSpace(
+                "Insufficient disk space to complete installation.",
+                suggested_action=["Check available disk space.", "Check size requirements for installation."],
+            ) from e
+        else:
+            raise
     except KeyboardInterrupt as e:
         close_worker_pool_on_exception(e)
         raise CliKeyboardInterrupt("Installer halted by keyboard interrupt.") from e


Test patch:
diff --git a/tests/test_install.py b/tests/test_install.py
index 81a650a..45e4535 100644
--- a/tests/test_install.py
+++ b/tests/test_install.py
@@ -1,3 +1,4 @@
+import errno
 import hashlib
 import logging
 import os
@@ -1156,10 +1157,10 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option
 
 
 @pytest.mark.parametrize(
-    "exception_class, settings_file, expect_end_msg, expect_return",
+    "exception, settings_file, expect_end_msg, expect_return",
     (
         (
-            RuntimeError,
+            RuntimeError(),
             "../aqt/settings.ini",
             "===========================PLEASE FILE A BUG REPORT===========================\n"
             "You have discovered a bug in aqt.\n"
@@ -1168,14 +1169,14 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option
             Cli.UNHANDLED_EXCEPTION_CODE,
         ),
         (
-            KeyboardInterrupt,
+            KeyboardInterrupt(),
             "../aqt/settings.ini",
             "WARNING : Caught KeyboardInterrupt, terminating installer workers\n"
             "ERROR   : Installer halted by keyboard interrupt.",
             1,
         ),
         (
-            MemoryError,
+            MemoryError(),
             "../aqt/settings.ini",
             "WARNING : Caught MemoryError, terminating installer workers\n"
             "ERROR   : Out of memory when downloading and extracting archives in parallel.\n"
@@ -1187,7 +1188,7 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option
             1,
         ),
         (
-            MemoryError,
+            MemoryError(),
             "data/settings_no_concurrency.ini",
             "WARNING : Caught MemoryError, terminating installer workers\n"
             "ERROR   : Out of memory when downloading and extracting archives.\n"
@@ -1197,11 +1198,39 @@ def test_install_nonexistent_archives(monkeypatch, capsys, cmd, xml_file: Option
             "(see https://aqtinstall.readthedocs.io/en/latest/cli.html#cmdoption-list-tool-external)",
             1,
         ),
+        (
+            OSError(errno.ENOSPC, "No space left on device"),
+            "../aqt/settings.ini",
+            "WARNING : Caught OSError, terminating installer workers\n"
+            "ERROR   : Insufficient disk space to complete installation.\n"
+            "==============================Suggested follow-up:==============================\n"
+            "* Check available disk space.\n"
+            "* Check size requirements for installation.",
+            1,
+        ),
+        (
+            OSError(),
+            "../aqt/settings.ini",
+            "===========================PLEASE FILE A BUG REPORT===========================\n"
+            "You have discovered a bug in aqt.\n"
+            "Please file a bug report at https://github.com/miurahr/aqtinstall/issues\n"
+            "Please remember to include a copy of this program's output in your report.",
+            Cli.UNHANDLED_EXCEPTION_CODE,
+        ),
+        (
+            PermissionError(),
+            "../aqt/settings.ini",
+            "WARNING : Caught PermissionError, terminating installer workers\n"
+            f"ERROR   : Failed to write to base directory at {os.getcwd()}\n"
+            "==============================Suggested follow-up:==============================\n"
+            "* Check that the destination is writable and does not already contain files owned by another user.",
+            1,
+        ),
     ),
 )
-def test_install_pool_exception(monkeypatch, capsys, exception_class, settings_file, expect_end_msg, expect_return):
+def test_install_pool_exception(monkeypatch, capsys, exception, settings_file, expect_end_msg, expect_return):
     def mock_installer_func(*args):
-        raise exception_class()
+        raise exception
 
     host, target, ver, arch = "windows", "desktop", "6.1.0", "win64_mingw81"
     updates_url = "windows_x86/desktop/qt6_610/Updates.xml"