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

436 Views Asked by At

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?

1

There are 1 best solutions below

0
AKX On BEST ANSWER

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"...