1 # Copyright 2012-2016 Tails developers <tails@boum.org>
2 # Copyright 2011 Max <govnototalitarizm@gmail.com>
3 # Copyright 2011 Martin Owens
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>
18 """Persistent Storage handling"""
22 from typing
import Optional
24 from gi
.repository
import Gio
, GLib
26 import tailsgreeter
.errors
27 import tps
.dbus
.errors
as tps_errors
28 from tailsgreeter
import config
# NOQA: E402
29 from tps
import InvalidBootDeviceErrorType
34 BUS_NAME
= "org.boum.tails.PersistentStorage"
35 OBJECT_PATH
= "/org/boum/tails/PersistentStorage"
36 INTERFACE_NAME
= "org.boum.tails.PersistentStorage"
39 class PersistentStorageSettings
:
40 """Controller for settings related to Persistent Storage"""
42 def __init__(self
) -> None:
43 self
.failed_with_unrecoverable_error
= False
44 self
.cleartext_name
= "TailsData_unlocked"
45 self
.cleartext_device
= "/dev/mapper/" + self
.cleartext_name
46 self
.service_proxy
= Gio
.DBusProxy
.new_sync(
47 Gio
.bus_get_sync(Gio
.BusType
.SYSTEM
, None),
48 Gio
.DBusProxyFlags
.NONE
,
54 ) # type: Gio.DBusProxy
55 device_variant
= self
.service_proxy
.get_cached_property("Device") # type: GLib.Variant
56 self
.device
= device_variant
.get_string() if device_variant
else "/"
57 self
.is_unlocked
= False
58 self
.is_created
= self
.service_proxy
.get_cached_property("IsCreated")
59 self
.can_unlock
= self
.service_proxy
.get_cached_property("CanUnlock")
60 self
.is_upgraded
= self
.service_proxy
.get_cached_property("IsUpgraded")
61 self
.error
: GLib
.Variant
= self
.service_proxy
.get_cached_property("Error")
62 self
.error_type
: InvalidBootDeviceErrorType |
None = None
64 self
.error_type
= InvalidBootDeviceErrorType(self
.error
.get_uint32())
65 self
.service_proxy
.connect("g-properties-changed", self
.on_properties_changed
)
67 def on_properties_changed(
70 changed_properties
: GLib
.Variant
,
71 invalidated_properties
: list[str],
73 """Callback for when the Persistent Storage properties change"""
74 logging
.debug("changed properties: %s", changed_properties
)
75 keys
= set(changed_properties
.keys())
77 if "CanUnlock" in keys
:
78 self
.can_unlock
= changed_properties
["CanUnlock"]
80 self
.error
= changed_properties
["Error"]
82 self
.error_type
= InvalidBootDeviceErrorType(self
.error
.get_uint32())
83 if "IsCreated" in keys
:
84 self
.is_created
= changed_properties
["IsCreated"]
85 if "IsUpgraded" in keys
:
86 self
.is_upgraded
= changed_properties
["IsUpgraded"]
88 self
.device
= changed_properties
["Device"]
90 def unlock(self
, passphrase
: str, forceful_fsck
: bool = False):
91 """Unlock the Persistent Storage partition
94 * WrongPassphraseError if the passphrase is incorrect.
95 * FilesystemErrorsLeftUncorrectedError if `e2fsck -f -p`
96 found some errors that it could not correct.
97 * PersistentStorageError if something else went wrong."""
98 logging
.debug("Unlocking Persistent Storage")
101 self
.service_proxy
.call_sync(
102 method_name
="Unlock",
103 parameters
=GLib
.Variant("(sb)", (passphrase
, forceful_fsck
)),
104 flags
=Gio
.DBusCallFlags
.NONE
,
105 # In some cases, the default timeout of 25 seconds was not
106 # enough, especially since we now run fsck as part of the unlock
107 # operation, so we use a larger timeout instead.
110 except GLib
.GError
as err
:
111 if tps_errors
.IncorrectPassphraseError
.is_instance(err
):
112 raise tailsgreeter
.errors
.WrongPassphraseError
from err
114 # All of the following errors are considered unrecoverable
115 # (so we don't ask the user to unlock before starting Tails)
116 self
.failed_with_unrecoverable_error
= True
118 if tps_errors
.IOErrorsDetectedError
.is_instance(err
):
119 raise tailsgreeter
.errors
.IOErrorsDetectedError
from err
121 if tps_errors
.FilesystemErrorsLeftUncorrectedError
.is_instance(err
):
122 raise tailsgreeter
.errors
.FilesystemErrorsLeftUncorrectedError
from err
124 raise tailsgreeter
.errors
.PersistentStorageError(
125 _("Error unlocking Persistent Storage: {}").format(err
)
127 self
.is_unlocked
= True
129 def upgrade_luks(self
, passphrase
):
130 """Upgrade the Persistent Storage to the latest format
133 WrongPassphraseError if the passphrase is incorrect.
134 PersistentStorageError if something else went wrong."""
135 logging
.debug("Upgrading Persistent Storage")
137 self
.service_proxy
.call_sync(
138 method_name
="UpgradeLUKS",
139 parameters
=GLib
.Variant("(s)", (passphrase
,)),
140 flags
=Gio
.DBusCallFlags
.NONE
,
141 # GLib.MAXINT (largest 32-bit signed integer) disables
143 timeout_msec
=GLib
.MAXINT
,
145 except GLib
.GError
as err
:
146 if tps_errors
.IncorrectPassphraseError
.is_instance(err
):
147 raise tailsgreeter
.errors
.WrongPassphraseError
from err
148 self
.failed_with_unrecoverable_error
= True
149 raise tailsgreeter
.errors
.PersistentStorageError(
150 _("Error upgrading Persistent Storage: {}").format(err
)
153 def activate_persistent_storage(self
):
154 """Activate the already unlocked Persistent Storage"""
156 self
.service_proxy
.call_sync(
157 method_name
="Activate",
159 flags
=Gio
.DBusCallFlags
.NONE
,
160 # We have seen this take almost 4 minutes (tails/tails#19944)
161 # so let's try 5 minutes for now. We don't want to disable
162 # the timeout completely (yet) since we still want to catch
163 # situations where e.g. some of our on-activation scripts
164 # gets in an infinite loop or similar.
167 except GLib
.GError
as err
:
168 if tps_errors
.FeatureActivationFailedError
.is_instance(err
):
169 tps_errors
.FeatureActivationFailedError
.strip_remote_error(err
)
170 features
= err
.message
.split(":")
171 # translate feature names
172 features
= [config
.gettext(feature
) for feature
in features
]
173 # Translators: Don't translate {features}, it's a placeholder
174 # and will be replaced.
175 msg
= config
.gettext(
176 "Failed to activate some features of the Persistent Storage: {features}."
177 ).format(features
=", ".join(features
))
178 raise tailsgreeter
.errors
.FeatureActivationFailedError(msg
) from err
179 self
.failed_with_unrecoverable_error
= True
180 raise tailsgreeter
.errors
.PersistentStorageError(
181 _("Error activating Persistent Storage: {}").format(err
)
184 def abort_repair_filesystem(self
):
185 """Abort any ongoing filesystem check on the Persistent
188 Raises PersistentStorageError if something went wrong."""
191 self
.service_proxy
.call_sync(
192 method_name
="AbortRepairFilesystem",
194 flags
=Gio
.DBusCallFlags
.NONE
,
195 # GLib.MAXINT (largest 32-bit signed integer) disables
197 timeout_msec
=GLib
.MAXINT
,
199 except GLib
.GError
as err
:
200 raise tailsgreeter
.errors
.PersistentStorageError(
202 "Failed to abort when repairing Persistent Storage filesystem: {}"
206 def repair_filesystem(self
, finish_callback
: callable) -> Gio
.Cancellable
:
207 """Asynchronously start a forceful filesystem check (e2fsck -f -y)
208 on the Persistent Storage.
210 The finish_callback is called once the operation has finished
211 and will receive any errors raised during the operation.
213 Returns a Gio.Cancellable so the caller can abort the
214 filesystem check by calling its cancel() method.
216 Raises PersistentStorageError if something went wrong."""
218 cancellable
= Gio
.Cancellable()
220 self
.service_proxy
.call(
221 method_name
="RepairFilesystem",
223 flags
=Gio
.DBusCallFlags
.NONE
,
224 # GLib.MAXINT (largest 32-bit signed integer) disables
226 timeout_msec
=GLib
.MAXINT
,
227 cancellable
=cancellable
,
228 callback
=finish_callback
,