Skip to content

Disk (Linux): Don't mark / as read-only in Fedora Atomic #1762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

ledif
Copy link

@ledif ledif commented May 17, 2025

Fedora Atomic and its derivatives such as Bazzite use a mix of composefs for the system mount and btrfs for variable read-write data. The composefs mount is correctly marked as read-only but the btrfs partition is also marked as read-only because the partition is mounted in several different places with a mix of read-write and read-only.

As an example:

/dev/nvme1n1p3 /sysroot btrfs ro,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/root 0 0
/dev/nvme1n1p3 /etc btrfs rw,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/root 0 0
/dev/nvme1n1p3 /sysroot/ostree/deploy/default/var btrfs rw,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/root 0 0
/dev/nvme1n1p3 /var btrfs rw,seclabel,noatime,ssd,discard=async,space_cache=v2,subvolid=256,subvol=/var 0 0
/dev/nvme1n1p3 /var/home btrfs rw,seclabel,noatime,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/home 0 0

The main btrfs partition should not be considered read-only because it happens to contain a read-only subvolume. This PR enhances the read-only detection logic to see if any of the mounts for the same device are read-write and if so mark the whole device as read-write.

Screenshots

Current default Bazzite fastfetch:

Screenshot_20250517_093613

Bazzite's fastfetch with the read-only mounts hidden. Notice that it hides both the composefs mount (which we want to hide) and the main btrfs mount (which we do not want to hide):
Screenshot_20250517_093557

Bazzite's fastfetch with this patch and read-only mounts hidden. Notice the main btrfs mount is there as expected:

Screenshot_20250517_102653

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adjusts read-only detection for Btrfs mounts on Fedora Atomic systems by checking all mount points for a device before marking it as read-only, preventing the main Btrfs partition from being hidden when only one subvolume is ro.

  • Added isReadOnly() helper that scans /proc/mounts for R/W instances of the same Btrfs device
  • Switched detectType() to use isReadOnly() instead of a direct ro option check
Comments suppressed due to low confidence (1)

src/detection/disk/disk_linux.c:221

  • Introduce unit tests for isReadOnly(), especially to verify behavior when the same Btrfs device has both read-only and read-write mounts.
static bool isReadOnly(FFDisk* currentDisk, struct mntent* device)

return false;

// For BTRFS on Fedora Atomic, check if there are other R/W mount points for the same device
if (ffStrbufEqualS(&currentDisk->filesystem, "btrfs"))
Copy link
Preview

Copilot AI May 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Avoid using the literal string "btrfs"; consider defining a constant or using an enum for filesystem types to improve readability and reduce risk of typos.

Suggested change
if (ffStrbufEqualS(&currentDisk->filesystem, "btrfs"))
if (ffStrbufEqualS(&currentDisk->filesystem, FILESYSTEM_BTRFS))

Copilot uses AI. Check for mistakes.

Copy link
Author

@ledif ledif May 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no FILESYSTEM_BTRFS enum defined anywhere and I do not believe it makes sense to create such an enum for an unbounded list of filesystem types.

Comment on lines +230 to +245
FILE* mountsFile = setmntent("/proc/mounts", "r");
if (mountsFile != NULL)
{
struct mntent* otherDev;
while ((otherDev = getmntent(mountsFile)))
{
// If same device but not read-only, then this device has R/W access
if (ffStrEquals(otherDev->mnt_fsname, deviceName) &&
!hasmntopt(otherDev, MNTOPT_RO))
{
endmntent(mountsFile);
return false;
}
}
endmntent(mountsFile);
}
Copy link
Preview

Copilot AI May 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Opening and scanning /proc/mounts on every call could add I/O overhead; consider caching the mounts list once per scan or reusing the open file between disk checks.

Suggested change
FILE* mountsFile = setmntent("/proc/mounts", "r");
if (mountsFile != NULL)
{
struct mntent* otherDev;
while ((otherDev = getmntent(mountsFile)))
{
// If same device but not read-only, then this device has R/W access
if (ffStrEquals(otherDev->mnt_fsname, deviceName) &&
!hasmntopt(otherDev, MNTOPT_RO))
{
endmntent(mountsFile);
return false;
}
}
endmntent(mountsFile);
}
static struct mntent** cachedMounts = NULL;
static size_t cachedMountsCount = 0;
if (cachedMounts == NULL)
{
FILE* mountsFile = setmntent("/proc/mounts", "r");
if (mountsFile != NULL)
{
struct mntent* entry;
size_t count = 0;
while ((entry = getmntent(mountsFile)))
count++;
rewind(mountsFile);
cachedMounts = malloc(count * sizeof(struct mntent*));
cachedMountsCount = count;
count = 0;
while ((entry = getmntent(mountsFile)))
{
cachedMounts[count] = malloc(sizeof(struct mntent));
*cachedMounts[count] = *entry;
count++;
}
endmntent(mountsFile);
}
}
for (size_t i = 0; i < cachedMountsCount; i++)
{
struct mntent* otherDev = cachedMounts[i];
// If same device but not read-only, then this device has R/W access
if (ffStrEquals(otherDev->mnt_fsname, deviceName) &&
!hasmntopt(otherDev, MNTOPT_RO))
{
return false;
}
}

Copilot uses AI. Check for mistakes.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I agree that this patch potentially introduces an additional overhead where under certain conditions, the /proc/mounts file will be processed multiple times, I would argue that this situation only occurs in systems where a device is mounted read-only which itself is a rare condition and occurs most consistently for immutable-type distributions such as Fedora Atomic.

If we denote the number of /proc/mount entries as n and the number of such entries that are read-only as k, this patch increases the asymptotic complexity of this function from O(n) to O(n*(k+1)). However, k is usually 0 for most non-immutable systems and this patch would introduce no overhead. Creating a static cache of mount entries in my opinion is overkill for such a rare scenario.

@CarterLi
Copy link
Member

Please upload your /proc/mounts

@ledif
Copy link
Author

ledif commented May 18, 2025

This is my full /proc/mounts for the Bazzite machine:

$ cat /proc/mounts 
/dev/nvme2n1p3 /sysroot btrfs ro,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/root 0 0
composefs / overlay ro,seclabel,relatime,lowerdir+=/run/ostree/.private/cfsroot-lower,datadir+=/sysroot/ostree/repo/objects,redirect_dir=on,metacopy=on 0 0
/dev/nvme2n1p3 /etc btrfs rw,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/root 0 0
/dev/nvme2n1p3 /sysroot/ostree/deploy/default/var btrfs rw,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/root 0 0
devtmpfs /dev devtmpfs rw,seclabel,nosuid,size=4096k,nr_inodes=8150247,mode=755,inode64 0 0
tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev,inode64 0 0
devpts /dev/pts devpts rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0
securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
cgroup2 /sys/fs/cgroup cgroup2 rw,seclabel,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot 0 0
pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0
efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0
bpf /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0
configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /run tmpfs rw,seclabel,nosuid,nodev,size=13086212k,nr_inodes=819200,mode=755,inode64 0 0
selinuxfs /sys/fs/selinux selinuxfs rw,nosuid,noexec,relatime 0 0
systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=37,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=14286 0 0
tracefs /sys/kernel/tracing tracefs rw,seclabel,nosuid,nodev,noexec,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0
mqueue /dev/mqueue mqueue rw,seclabel,nosuid,nodev,noexec,relatime 0 0
hugetlbfs /dev/hugepages hugetlbfs rw,seclabel,nosuid,nodev,relatime,pagesize=2M 0 0
tmpfs /run/credentials/systemd-journald.service tmpfs ro,seclabel,nosuid,nodev,noexec,relatime,nosymfollow,size=1024k,nr_inodes=1024,mode=700,inode64,noswap 0 0
fusectl /sys/fs/fuse/connections fusectl rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /tmp tmpfs rw,seclabel,nosuid,nodev,nr_inodes=1048576,inode64 0 0
/dev/nvme2n1p3 /var btrfs rw,seclabel,noatime,ssd,discard=async,space_cache=v2,subvolid=256,subvol=/var 0 0
systemd-1 /var/mnt/bigbend autofs rw,relatime,fd=80,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=22544 0 0
systemd-1 /var/mnt/conf autofs rw,relatime,fd=81,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=22546 0 0
systemd-1 /var/mnt/documents autofs rw,relatime,fd=88,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=22549 0 0
systemd-1 /var/mnt/home autofs rw,relatime,fd=89,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=22552 0 0
systemd-1 /var/mnt/media autofs rw,relatime,fd=90,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=22554 0 0
/dev/nvme2n1p2 /boot ext4 rw,seclabel,relatime 0 0
overlay /usr/share/sddm/themes overlay rw,seclabel,relatime,lowerdir=/usr/share/sddm/themes,upperdir=/var/sddm_themes/themes,workdir=/var/sddm_themes/themes.work 0 0
/dev/nvme2n1p3 /var/home btrfs rw,seclabel,noatime,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/home 0 0
/dev/nvme0n1p1 /var/mnt/nvme ext4 rw,seclabel,relatime 0 0
/dev/nvme2n1p1 /boot/efi vfat rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=winnt,errors=remount-ro 0 0
/dev/sda3 /var/mnt/ublue-dev btrfs rw,seclabel,relatime,ssd,discard=async,space_cache=v2,subvolid=5,subvol=/ 0 0
/dev/sdb1 /var/mnt/scratch ext4 rw,seclabel,relatime 0 0
tmpfs /run/credentials/systemd-resolved.service tmpfs ro,seclabel,nosuid,nodev,noexec,relatime,nosymfollow,size=1024k,nr_inodes=1024,mode=700,inode64,noswap 0 0
binfmt_misc /proc/sys/fs/binfmt_misc binfmt_misc rw,nosuid,nodev,noexec,relatime 0 0
sunrpc /var/lib/nfs/rpc_pipefs rpc_pipefs rw,relatime 0 0
//10.0.1.203/documents /var/mnt/documents cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,username=afidel,domain=workgroup,uid=1000,forceuid,gid=1000,forcegid,addr=10.0.1.203,file_mode=0755,dir_mode=0755,iocharset=utf8,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0
//10.0.1.203/home /var/mnt/home cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,username=afidel,domain=workgroup,uid=1000,forceuid,gid=1000,forcegid,addr=10.0.1.203,file_mode=0755,dir_mode=0755,iocharset=utf8,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0
//10.0.1.203/conf /var/mnt/conf cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,username=afidel,domain=workgroup,uid=1000,forceuid,gid=1000,forcegid,addr=10.0.1.203,file_mode=0755,dir_mode=0755,iocharset=utf8,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0
//10.0.1.203/media /var/mnt/media cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,username=afidel,domain=workgroup,uid=1000,forceuid,gid=1000,forcegid,addr=10.0.1.203,file_mode=0755,dir_mode=0755,iocharset=utf8,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0
//10.0.1.203/bigbend /var/mnt/bigbend cifs rw,relatime,vers=3.1.1,cache=strict,upcall_target=app,username=ci_user,domain=workgroup,uid=1000,forceuid,gid=1000,forcegid,addr=10.0.1.203,file_mode=0755,dir_mode=0755,iocharset=utf8,soft,nounix,serverino,mapposix,reparse=nfs,rsize=4194304,wsize=4194304,bsize=1048576,retrans=1,echo_interval=60,actimeo=1,closetimeo=1 0 0
tmpfs /run/user/1000 tmpfs rw,seclabel,nosuid,nodev,relatime,size=6543104k,nr_inodes=1635776,mode=700,uid=1000,gid=1000,inode64 0 0
portal /run/user/1000/doc fuse.portal rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0

And this is how it's displayed in fastfetch
image

@CarterLi
Copy link
Member

So your real requirement is to hide the overlay partition. However because fastfetch doesn't have a option to hide a specific partition, you come up with a idea which is to hide read-only partitions. And because the btrfs partition is also detected as read-only but you don't want to hide it, you created this PR to make the btrfs partition not read-only somehow.

the btrfs partition is also marked as read-only because the partition is mounted in several different places with a mix of read-write and read-only.
The main btrfs partition should not be considered read-only because it happens to contain a read-only subvolume

Mountpoint /sysroot is read-only. Others aren't. You see /sysroot only by default because it comes first in /proc/mounts, but it doesn't mean that the entire btrfs partition is considered read-only. You can always use --disk-show-subvolumes to make fastfetch print other mountpoints such as /etc and /var.

The better option is to use Btrfs module. Btrfs is the dedicated module which reports the disk usage of the entire btrfs partition, but not for any specific mountpoints and have no read-only issues.

Anyway, this PR seems a dirty hack for a very specific distro and a very specific reason. I don't like it.

@ledif
Copy link
Author

ledif commented May 19, 2025

This is what it looks like by setting a showReadOnly to false in the disks module and adding a Btrfs module after disks:

image

This looks good in the sense that it doesn't show the composefs parititon, but in my opinion, it is more confusing in other ways. Namely:

  • My non-system Btrfs disk shows up twice: once in the disk module and once in the Btrfs module
  • The Btrfs disks have a number after their symbol, which is a departure from the disks module and may raise questions about why some disks have numbers in front of them and some don't

Perhaps a different approach could be one where we can use both a Btrfs module and a Disks module and somehow hide the Btrfs partitions in the Disks module. Is that possible? If not, does it make sense to implement something like that?

@ledif ledif marked this pull request as draft May 20, 2025 13:24
@CarterLi CarterLi closed this in a0f5790 May 21, 2025
@CarterLi
Copy link
Member

  1. --disk-hide-folder /:/sysroot
  2. --disk-hide-fs overlay:btrfs

Both can be used to hide the partitions mountpoints

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants