diff --git a/core/src/apps/common/confirm.py b/core/src/apps/common/confirm.py index dabcecaa26..5602e9d126 100644 --- a/core/src/apps/common/confirm.py +++ b/core/src/apps/common/confirm.py @@ -78,8 +78,8 @@ async def info_confirm( async def hold_to_confirm( - ctx: wire.Context, - content: ui.Layout, + ctx: wire.GenericContext, + content: ui.Component, code: EnumTypeButtonRequestType = ButtonRequestType.Other, confirm: str = HoldToConfirm.DEFAULT_CONFIRM, confirm_style: ButtonStyleType = HoldToConfirm.DEFAULT_CONFIRM_STYLE, diff --git a/core/src/apps/common/request_pin.py b/core/src/apps/common/request_pin.py index 36cc7eaa4c..1de809be72 100644 --- a/core/src/apps/common/request_pin.py +++ b/core/src/apps/common/request_pin.py @@ -7,7 +7,7 @@ from trezor.ui.pin import CANCELLED, PinDialog from trezor.ui.popup import Popup from trezor.ui.text import Text -from apps.common.sd_salt import SdProtectCancelled, request_sd_salt +from apps.common.sdcard import SdCardUnavailable, request_sd_salt if False: from typing import Any, Optional, Tuple @@ -95,7 +95,7 @@ async def verify_user_pin( ) -> None: try: salt = await request_sd_salt() - except SdProtectCancelled: + except SdCardUnavailable: raise PinCancelled if not config.has_pin() and not config.check_pin(pin_to_int(""), salt): diff --git a/core/src/apps/common/sd_salt.py b/core/src/apps/common/sdcard.py similarity index 50% rename from core/src/apps/common/sd_salt.py rename to core/src/apps/common/sdcard.py index aa43a45a02..34c9a667dc 100644 --- a/core/src/apps/common/sd_salt.py +++ b/core/src/apps/common/sdcard.py @@ -1,15 +1,15 @@ import storage.sd_salt from storage.sd_salt import SD_CARD_HOT_SWAPPABLE -from trezor import io, ui, wire +from trezor import sdcard, ui, wire from trezor.ui.text import Text -from apps.common.confirm import confirm +from apps.common.confirm import confirm, hold_to_confirm if False: from typing import Optional -class SdProtectCancelled(wire.ProcessError): +class SdCardUnavailable(wire.ProcessError): pass @@ -45,17 +45,57 @@ async def insert_card_dialog(ctx: wire.GenericContext) -> bool: return await confirm(ctx, text, confirm=btn_confirm, cancel=btn_cancel) +async def format_card_dialog(ctx: wire.GenericContext) -> bool: + # Format card? yes/no + text = Text("SD card error", ui.ICON_WRONG, ui.RED) + text.bold("Unknown filesystem.") + text.br_half() + text.normal("Use a different card or") + text.normal("format the SD card to") + text.normal("the FAT32 filesystem.") + if not await confirm(ctx, text, confirm="Format", cancel="Cancel"): + return False + + # Confirm formatting + text = Text("Format SD card", ui.ICON_WIPE, ui.RED) + text.normal("Do you really want to", "format the SD card?") + text.br_half() + text.bold("All data on the SD card", "will be lost.") + return await hold_to_confirm(ctx, text, confirm="Format SD card") + + async def sd_problem_dialog(ctx: wire.GenericContext) -> bool: - text = Text("SD card protection", ui.ICON_WRONG, ui.RED) + text = Text("SD card problem", ui.ICON_WRONG, ui.RED) text.normal("There was a problem", "accessing the SD card.") return await confirm(ctx, text, confirm="Retry", cancel="Abort") -async def ensure_sd_card(ctx: wire.GenericContext) -> None: - sd = io.SDCard() - while not sd.present(): +async def ensure_sdcard(ctx: wire.GenericContext) -> None: + while not sdcard.is_present(): if not await insert_card_dialog(ctx): - raise SdProtectCancelled("SD card required.") + raise SdCardUnavailable("SD card required.") + + while True: + try: + with sdcard.get_filesystem(mounted=False) as fs: + fs.mount() + # Mount succeeded, filesystem is OK + return + except OSError: + # Mount failed. Handle the problem outside except-clause + pass + + if not await format_card_dialog(ctx): + raise SdCardUnavailable("SD card not formatted.") + + try: + with sdcard.get_filesystem(mounted=False) as fs: + fs.mkfs() + # mkfs succeeded. Re-run loop to retry mounting. + continue + except OSError: + if not await sd_problem_dialog(ctx): + raise SdCardUnavailable("Problem formatting SD card.") async def request_sd_salt( @@ -65,16 +105,15 @@ async def request_sd_salt( return None while True: - await ensure_sd_card(ctx) + await ensure_sdcard(ctx) try: return storage.sd_salt.load_sd_salt() except storage.sd_salt.WrongSdCard: if not await _wrong_card_dialog(ctx): - raise SdProtectCancelled("Wrong SD card.") + raise SdCardUnavailable("Wrong SD card.") except OSError: - # Either the SD card did not power on, or the filesystem could not be - # mounted (card is not formatted?), or there is a staged salt file and - # we could not commit it. + # Generic problem with loading the SD salt (either we could not read the + # file, or there is a staged salt which cannot be committed). # In either case, there is no good way to recover. If the user clicks Retry, # we will try again. if not await sd_problem_dialog(ctx): diff --git a/core/src/apps/homescreen/__init__.py b/core/src/apps/homescreen/__init__.py index 1eabc87191..91d11448cd 100644 --- a/core/src/apps/homescreen/__init__.py +++ b/core/src/apps/homescreen/__init__.py @@ -3,7 +3,7 @@ import storage.device import storage.recovery import storage.sd_salt from storage import cache -from trezor import config, io, utils, wire +from trezor import config, sdcard, utils, wire from trezor.messages import Capability, MessageType from trezor.messages.Features import Features from trezor.messages.Success import Success @@ -69,7 +69,7 @@ def get_features() -> Features: Capability.ShamirGroups, Capability.PassphraseEntry, ] - f.sd_card_present = io.SDCard().present() + f.sd_card_present = sdcard.is_present() f.sd_protection = storage.sd_salt.is_enabled() f.wipe_code_protection = config.has_wipe_code() f.session_id = cache.get_session_id() diff --git a/core/src/apps/management/sd_protect.py b/core/src/apps/management/sd_protect.py index 02cad3ce01..071d3a472f 100644 --- a/core/src/apps/management/sd_protect.py +++ b/core/src/apps/management/sd_protect.py @@ -14,7 +14,7 @@ from apps.common.request_pin import ( request_pin_and_sd_salt, show_pin_invalid, ) -from apps.common.sd_salt import ensure_sd_card, sd_problem_dialog +from apps.common.sdcard import ensure_sdcard, sd_problem_dialog if False: from typing import Awaitable, Tuple @@ -32,7 +32,7 @@ async def _set_salt( ctx: wire.Context, salt: bytes, salt_tag: bytes, stage: bool = False ) -> None: while True: - await ensure_sd_card(ctx) + await ensure_sdcard(ctx) try: return storage.sd_salt.set_sd_salt(salt, salt_tag, stage) except OSError: @@ -62,7 +62,7 @@ async def sd_protect_enable(ctx: wire.Context, msg: SdProtect) -> Success: await require_confirm_sd_protect(ctx, msg) # Make sure SD card is present. - await ensure_sd_card(ctx) + await ensure_sdcard(ctx) # Get the current PIN. if config.has_pin(): @@ -133,7 +133,7 @@ async def sd_protect_refresh(ctx: wire.Context, msg: SdProtect) -> Success: await require_confirm_sd_protect(ctx, msg) # Make sure SD card is present. - await ensure_sd_card(ctx) + await ensure_sdcard(ctx) # Get the current PIN and salt from the SD card. pin, old_salt = await request_pin_and_sd_salt(ctx, "Enter PIN") diff --git a/core/src/boot.py b/core/src/boot.py index 8cd1a81fe0..32d910cbf6 100644 --- a/core/src/boot.py +++ b/core/src/boot.py @@ -5,7 +5,7 @@ from trezor import config, log, loop, res, ui, utils from trezor.pin import pin_to_int, show_pin_timeout from apps.common.request_pin import PinCancelled, request_pin -from apps.common.sd_salt import SdProtectCancelled, request_sd_salt +from apps.common.sdcard import SdCardUnavailable, request_sd_salt async def bootscreen() -> None: @@ -31,7 +31,7 @@ async def bootscreen() -> None: return else: label = "Wrong PIN, enter again" - except (OSError, PinCancelled, SdProtectCancelled) as e: + except (OSError, PinCancelled, SdCardUnavailable) as e: if __debug__: log.exception(__name__, e) except BaseException as e: