storage: Show notification when rootfs is read-only

Tests:

- Change the partition to test to '/mnt'. Mount a loopback filesystem on /mnt.
'dd if=/dev/zero of=/test-file bs=1M count=100; mkfs.ext4 /test-file; mount -o
loop /test-file /mnt'. Turn it to read-only with 'mount -o remount,ro /mnt'.
Wait about 3 minutes for the notification to show up.

- The notification shows icon, title and message as expected. The button power
app appears and works as expected.

- When the filesystem is mount rw again, the notification goes away in 3
minutes. 'mount -o remount,rw /mnt'.

[sunil: Let glib.schedule decide time when debugging]
[sunil: Perform exact matching in partition mount options]
[sunil: Simplify notification message. Minor grammar change]
[sunil: Minor refactoring for styling]
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
rsquared 2023-12-08 14:33:38 -07:00 committed by Sunil Mohan Adapa
parent c8dac10308
commit 92492d2449
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
2 changed files with 73 additions and 1 deletions

View File

@ -68,6 +68,9 @@ class StorageApp(app_module.App):
# Schedule initialization of UDisks2 initialization
glib.schedule(3, udisks2.init, repeat=False)
# Check periodically for a read-only root filesystem
glib.schedule(600, _warn_about_read_only_filesystem)
def setup(self, old_version):
"""Install and configure the app."""
super().setup(old_version)
@ -271,7 +274,7 @@ def get_error_message(error):
return message_map.get(short_error, error)
def warn_about_low_disk_space(request):
def warn_about_low_disk_space(_data):
"""Warn about insufficient space on root partition."""
from plinth.notification import Notification
@ -341,3 +344,55 @@ def report_failing_drive(id, is_failing):
'type': 'dismiss'
}], data=data, group='admin')
note.dismiss(should_dismiss=not is_failing)
def is_partition_read_only(mount_point='/'):
"""Return True if the partition is mounted read-only."""
for partition in psutil.disk_partitions(all=True):
if partition.mountpoint == mount_point:
return 'ro' in partition.opts.split(',')
raise ValueError('No such mount point')
def _warn_about_read_only_filesystem(_data):
"""Create a notification if the root filesystem is mounted read-only.
Remove the notification (if any) if the root filesystem is writable.
"""
from plinth.notification import Notification
show = is_partition_read_only()
notification_id = 'storage-read-only-root-filesystem'
if not show:
try:
Notification.get(notification_id).delete()
except KeyError:
pass
return
message = gettext_noop(
# xgettext:no-python-format
'You cannot save configuration changes. Try rebooting the system. If '
'the problem persists after a reboot, check the storage device for '
'errors.')
title = gettext_noop('Read-only root filesystem')
data = {
'app_icon': 'fa-hdd-o',
'app_name': 'translate:' + gettext_noop('Storage'),
}
# This is a serious issue so the notification can't be dismissed.
actions = [{
'type': 'link',
'class': 'primary',
'text': gettext_noop('Go to Power'),
'url': 'power:index'
}]
Notification.update_or_create(id=notification_id, app_id='storage',
severity='error', title=title,
message=message, actions=actions, data=data,
group='admin')

View File

@ -7,9 +7,12 @@ import contextlib
import re
import subprocess
import tempfile
from unittest.mock import patch
import psutil
import pytest
from plinth.modules import storage
from plinth.modules.storage import privileged
pytestmark = pytest.mark.usefixtures('mock_privileged')
@ -343,3 +346,17 @@ def test_validate_directory_writable(path, error):
def test_validate_directory_creatable(path, error):
"""Test that directory creatable validation returns expected output."""
_assert_validate_directory(path, error, check_creatable=True)
@patch('psutil.disk_partitions')
def test_is_partition_read_only(disk_partitions):
"""Test whether checking for ro partition works."""
partition = psutil._common.sdiskpart # pylint: disable=protected-access
disk_partitions.return_value = [
partition('/dev/root', '/', 'btrfs', 'rw,nosuid', 42, 42),
partition('/dev/root', '/foo', 'btrfs', 'rw', 321, 321),
partition('/dev/foo', '/bar', 'extfs', 'ro', 123, 123)
]
assert not storage.is_partition_read_only('/')
assert not storage.is_partition_read_only('/foo')
assert storage.is_partition_read_only('/bar')