Skip to content

Commit

Permalink
Temporary directory to executor (#5673)
Browse files Browse the repository at this point in the history
* Move temporary directory usage to executor

* Use temp_folder.name in Path constructor
  • Loading branch information
mdegat01 authored Feb 27, 2025
1 parent c5d4ebc commit 151d4bd
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 43 deletions.
14 changes: 12 additions & 2 deletions supervisor/addons/addon.py
Original file line number Diff line number Diff line change
Expand Up @@ -977,11 +977,21 @@ async def install_apparmor(self) -> None:
return

# Need install/update
with TemporaryDirectory(dir=self.sys_config.path_tmp) as tmp_folder:
profile_file = Path(tmp_folder, "apparmor.txt")
tmp_folder: TemporaryDirectory | None = None

def install_update_profile() -> Path:
nonlocal tmp_folder
tmp_folder = TemporaryDirectory(dir=self.sys_config.path_tmp)
profile_file = Path(tmp_folder.name, "apparmor.txt")
adjust_profile(self.slug, self.path_apparmor, profile_file)
return profile_file

try:
profile_file = await self.sys_run_in_executor(install_update_profile)
await self.sys_host.apparmor.load_profile(self.slug, profile_file)
finally:
if tmp_folder:
await self.sys_run_in_executor(tmp_folder.cleanup)

async def uninstall_apparmor(self) -> None:
"""Remove AppArmor profile for Add-on."""
Expand Down
57 changes: 36 additions & 21 deletions supervisor/api/backups.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import asyncio
from collections.abc import Callable
import errno
from io import IOBase
import logging
from pathlib import Path
import re
Expand Down Expand Up @@ -518,29 +519,28 @@ async def upload(self, request: web.Request):
except vol.Invalid as ex:
raise APIError(humanize_error(filename, ex)) from None

with TemporaryDirectory(dir=tmp_path.as_posix()) as temp_dir:
tar_file = Path(temp_dir, "backup.tar")
temp_dir: TemporaryDirectory | None = None
backup_file_stream: IOBase | None = None

def open_backup_file() -> Path:
nonlocal temp_dir, backup_file_stream
temp_dir = TemporaryDirectory(dir=tmp_path.as_posix())
tar_file = Path(temp_dir.name, "backup.tar")
backup_file_stream = tar_file.open("wb")
return tar_file

def close_backup_file() -> None:
if backup_file_stream:
backup_file_stream.close()
if temp_dir:
temp_dir.cleanup()

try:
reader = await request.multipart()
contents = await reader.next()
try:
with tar_file.open("wb") as backup:
while True:
chunk = await contents.read_chunk()
if not chunk:
break
backup.write(chunk)

except OSError as err:
if err.errno == errno.EBADMSG and location in {
LOCATION_CLOUD_BACKUP,
None,
}:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
_LOGGER.error("Can't write new backup file: %s", err)
return False

except asyncio.CancelledError:
return False
tar_file = await self.sys_run_in_executor(open_backup_file)
while chunk := await contents.read_chunk(size=2**16):
await self.sys_run_in_executor(backup_file_stream.write, chunk)

backup = await asyncio.shield(
self.sys_backups.import_backup(
Expand All @@ -550,6 +550,21 @@ async def upload(self, request: web.Request):
additional_locations=locations,
)
)
except OSError as err:
if err.errno == errno.EBADMSG and location in {
LOCATION_CLOUD_BACKUP,
None,
}:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
_LOGGER.error("Can't write new backup file: %s", err)
return False

except asyncio.CancelledError:
return False

finally:
if temp_dir or backup:
await self.sys_run_in_executor(close_backup_file)

if backup:
return {ATTR_SLUG: backup.slug}
Expand Down
48 changes: 29 additions & 19 deletions supervisor/supervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,25 +158,35 @@ async def update_apparmor(self) -> None:
) from err

# Load
with TemporaryDirectory(dir=self.sys_config.path_tmp) as tmp_dir:
profile_file = Path(tmp_dir, "apparmor.txt")
try:
profile_file.write_text(data, encoding="utf-8")
except OSError as err:
if err.errno == errno.EBADMSG:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
raise SupervisorAppArmorError(
f"Can't write temporary profile: {err!s}", _LOGGER.error
) from err

try:
await self.sys_host.apparmor.load_profile(
"hassio-supervisor", profile_file
)
except HostAppArmorError as err:
raise SupervisorAppArmorError(
"Can't update AppArmor profile!", _LOGGER.error
) from err
temp_dir: TemporaryDirectory | None = None

def write_profile() -> Path:
nonlocal temp_dir
temp_dir = TemporaryDirectory(dir=self.sys_config.path_tmp)
profile_file = Path(temp_dir.name, "apparmor.txt")
profile_file.write_text(data, encoding="utf-8")
return profile_file

try:
profile_file = await self.sys_run_in_executor(write_profile)

await self.sys_host.apparmor.load_profile("hassio-supervisor", profile_file)

except OSError as err:
if err.errno == errno.EBADMSG:
self.sys_resolution.unhealthy = UnhealthyReason.OSERROR_BAD_MESSAGE
raise SupervisorAppArmorError(
f"Can't write temporary profile: {err!s}", _LOGGER.error
) from err

except HostAppArmorError as err:
raise SupervisorAppArmorError(
"Can't update AppArmor profile!", _LOGGER.error
) from err

finally:
if temp_dir:
await self.sys_run_in_executor(temp_dir.cleanup)

async def update(self, version: AwesomeVersion | None = None) -> None:
"""Update Supervisor version."""
Expand Down
6 changes: 5 additions & 1 deletion supervisor/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def remove_folder(
Is needed to avoid issue with:
- CAP_DAC_OVERRIDE
- CAP_DAC_READ_SEARCH
Must be run in executor.
"""
find_args = []
if content_only:
Expand All @@ -114,7 +115,10 @@ def remove_folder_with_excludes(
excludes: list[str],
tmp_dir: Path | None = None,
) -> None:
"""Remove folder with excludes."""
"""Remove folder with excludes.
Must be run in executor.
"""
with TemporaryDirectory(dir=tmp_dir) as temp_path:
temp_path = Path(temp_path)
moved_files: list[Path] = []
Expand Down

0 comments on commit 151d4bd

Please sign in to comment.