pythonmacosmounted-volumescd-rom

On Mac OS with python how to list only writable volumes?


On Mac OS with python how to list only writable volumes? In other words, in the /Volumes folder I want to list only the (partitions and pendrives) rw I don't want to list CDROM drives or mounted ISO images.

In linux there is the file '/proc/mounts' which displays the mounted drives with the type of partition and the mount options, in mac OS is there something similar? In linux I use it like this:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

from pathlib import Path
import getpass

def get_lst_writable_linux_disks():
    this_user = getpass.getuser()

    lst_available_linux_disks = [str(Path.home())]
    with open('/proc/mounts','r') as f:
        data = f.readlines()

    for line in data:
        item = line.split(' ')
        mount_point = item[1]
        fs_type = item[2]
        options = item[3]
        if mount_point.startswith('/mnt') or (mount_point.startswith(f'/media/{this_user}') and fs_type != 'vfat' and 'rw' in options):
            lst_available_linux_disks.append(mount_point)

    return lst_available_linux_disks

print(get_lst_writable_linux_disks())

How would I do the same on Mac OS?


Solution

  • For a more bullet-proof method than parsing the human-readable output of diskutil info -all, you could do something like this... (I don't quite like Apple's XML plist format; you'd think there was a better way to represent a dict than a flat key-value-key-value-... structure...)

    import subprocess
    import xml.etree.ElementTree as ET
    from typing import Tuple, List, Dict
    
    
    def plist_dict_to_dict(node: ET.Element) -> Dict[str, ET.Element]:
        assert node.tag == "dict"
        dct = {}
        current_key = None
        for i, el in enumerate(node):
            if i % 2 == 0:
                assert el.tag == "key"
                current_key = el.text
            else:
                assert current_key
                dct[current_key] = el
        return dct
    
    
    def get_volume_names() -> List[str]:
        command = ["/usr/sbin/diskutil", "list", "-plist"]
        volumes_xml = ET.fromstring(subprocess.check_output(command, encoding="utf-8"))
        volumes_info = plist_dict_to_dict(volumes_xml.find("dict"))
        vfd_array = volumes_info["VolumesFromDisks"]
        assert vfd_array.tag == "array"
        return [v.text for v in vfd_array.findall("string")]
    
    
    def get_volume_info(volume_name: str) -> Dict[str, ET.Element]:
        command = ["/usr/sbin/diskutil", "info", "-plist", volume_name]
        vol_info_xml = ET.fromstring(subprocess.check_output(command, encoding="utf-8"))
        return plist_dict_to_dict(vol_info_xml.find("dict"))
    
    
    def get_volume_flags(volume_name: str) -> Dict[str, bool]:
        vol_info = get_volume_info(volume_name)
        flags = {}
        for key, value in vol_info.items():
            if value.tag in ("true", "false"):
                flags[key] = value.tag == "true"
        return flags
    
    
    if __name__ == "__main__":
        for volume_name in get_volume_names():
            print(volume_name, ":", get_volume_flags(volume_name))
    

    On my machine, this prints out

    Volume Macintosh HD : {'AESHardware': True, 'Bootable': True, 'CanBeMadeBootable': False, 'CanBeMadeBootableRequiresDestroy': False, 'Ejectable': False, 'EjectableMediaAutomaticUnderSoftwareControl': False, 'EjectableOnly': False, 'Encryption': True, 'FileVault': True, 'Fusion': False, 'GlobalPermissionsEnabled': True, 'Internal': True, 'Locked': False, 'PartitionMapPartition': False, 'RAIDMaster': False, 'RAIDSlice': False, 'Removable': False, 'RemovableMedia': False, 'RemovableMediaOrExternalDevice': False, 'SolidState': True, 'SupportsGlobalPermissionsDisable': True, 'SystemImage': False, 'WholeDisk': False, 'Writable': False, 'WritableMedia': True, 'WritableVolume': False}
    Volume Macintosh HD - Data : {'AESHardware': True, 'Bootable': True, 'CanBeMadeBootable': False, 'CanBeMadeBootableRequiresDestroy': False, 'Ejectable': False, 'EjectableMediaAutomaticUnderSoftwareControl': False, 'EjectableOnly': False, 'Encryption': True, 'FileVault': True, 'Fusion': False, 'GlobalPermissionsEnabled': True, 'Internal': True, 'Locked': False, 'PartitionMapPartition': False, 'RAIDMaster': False, 'RAIDSlice': False, 'Removable': False, 'RemovableMedia': False, 'RemovableMediaOrExternalDevice': False, 'SolidState': True, 'SupportsGlobalPermissionsDisable': True, 'SystemImage': False, 'WholeDisk': False, 'Writable': True, 'WritableMedia': True, 'WritableVolume': True}
    Volume Recovery : {'AESHardware': True, 'Bootable': False, 'CanBeMadeBootable': False, 'CanBeMadeBootableRequiresDestroy': False, 'Ejectable': False, 'EjectableMediaAutomaticUnderSoftwareControl': False, 'EjectableOnly': False, 'Encryption': False, 'FileVault': False, 'Fusion': False, 'GlobalPermissionsEnabled': True, 'Internal': True, 'Locked': False, 'PartitionMapPartition': False, 'RAIDMaster': False, 'RAIDSlice': False, 'Removable': False, 'RemovableMedia': False, 'RemovableMediaOrExternalDevice': False, 'SolidState': True, 'SupportsGlobalPermissionsDisable': True, 'SystemImage': False, 'WholeDisk': False, 'Writable': True, 'WritableMedia': True, 'WritableVolume': True}
    Volume VM : {'AESHardware': True, 'Bootable': False, 'CanBeMadeBootable': False, 'CanBeMadeBootableRequiresDestroy': False, 'Ejectable': False, 'EjectableMediaAutomaticUnderSoftwareControl': False, 'EjectableOnly': False, 'Encryption': True, 'FileVault': False, 'Fusion': False, 'GlobalPermissionsEnabled': True, 'Internal': True, 'Locked': False, 'PartitionMapPartition': False, 'RAIDMaster': False, 'RAIDSlice': False, 'Removable': False, 'RemovableMedia': False, 'RemovableMediaOrExternalDevice': False, 'SolidState': True, 'SupportsGlobalPermissionsDisable': True, 'SystemImage': False, 'WholeDisk': False, 'Writable': True, 'WritableMedia': True, 'WritableVolume': True}
    

    so besides "Writable" you will probably want to look at "Internal"...