在macOS Catalina,Big Sur和更高版本中确定卷组

问题描述

从macOS 10.15(Catalina)开始,一个卷(如用户所见)实际上可能由多个卷组成,例如“系统”卷和“数据”卷。

我正在编写一个工具,该工具需要分别识别这些卷,因为使用searchfsfts_read这样的特定文件操作时,这些操作不会跨越这些卷的边界,因此我需要了解卷属于同一类,因此,当用户搜索系统卷时,我知道在文件操作中同时包括“系统”卷和“数据”卷。

如何安全地确定哪些卷属于同一卷?

仅使用[NSFileManager mountedVolumeURLsIncludingResourceValuesForKeys:options:]并不会带来太多帮助,因为它不会包括位于/System/Volumes/Data的根系统的Data卷(但可能包括隐藏的/System/Volumes/Data/home卷)。使用命令行工具(例如df)也是如此。

我需要考虑当前未引导的其他系统卷。例如,如果我同时拥有BigSur和Catalina系统,并且已经从前者启动,那么我希望能够识别出这四个卷:

/                              BigSur System volume
/System/Volumes/Data           BigSur Data volume
/Volumes/Catalina              Catalina System volume
/Volumes/Catalina - Daten      Catalina Data volume (created on a German system)

我如何确定包含“ Catalina”的两个卷实际上属于同一组?我不喜欢用部分名称来匹配它们,因为这对我来说似乎是随机的并且不可靠。而且,如果不是在英语系统上创建的话,数据量甚至都不会使用名称中的“ Data”这一事实,这使得它已经非常难以解决

也许还有其他一些卷属性可以帮助识别这些卷组吗?

解决方法

Mike Bombich为我提供了此解决方案:

您可以从IOKit获取卷UUID和卷组UUID。同一组中的两个卷将具有相同的组UUID。请注意,组UUID始终与数据卷的UUID相同(至少在实践中是这样)。

以下是获取已安装卷列表的代码,包括作为卷组一部分的隐藏卷:

- (void)listVolumes
{
    NSArray<NSURL*> *vols = [NSFileManager.defaultManager mountedVolumeURLsIncludingResourceValuesForKeys:nil options: 0 ];
    vols = [vols arrayByAddingObject:[NSURL fileURLWithPath:@"/System/Volumes/Data"]]; // the root's Data vol isn't added by default
    NSMutableArray<NSString*> *lines = [NSMutableArray new];
    for (NSURL *vol in vols) {
        NSDictionary *d = [vol resourceValuesForKeys:@[
            NSURLVolumeIsBrowsableKey,NSURLVolumeIsRootFileSystemKey,NSURLVolumeIdentifierKey,NSURLVolumeNameKey
        ] error:nil];

        struct statfs fsinfo;
        statfs(vol.path.UTF8String,&fsinfo);
        NSString *bsdName = [NSString stringWithUTF8String:fsinfo.f_mntfromname];
        bsdName = [bsdName lastPathComponent];

        [lines addObject:[NSString stringWithFormat:@"%@,%@,%@",bsdName,vol.path,d[NSURLVolumeIsBrowsableKey],d[NSURLVolumeNameKey]]];
    }
    NSLog(@"\n%@",[lines componentsJoinedByString:@"\n"]);
}

以及用于列出卷组ID及其角色的代码:

- (void)listGroupIDs
{
    io_iterator_t iterator; io_object_t obj;
    IOServiceGetMatchingServices (kIOMasterPortDefault,IOServiceMatching("IOMediaBSDClient"),&iterator);
    while ((obj = IOIteratorNext (iterator)) != 0) {
        io_object_t obj2;
        IORegistryEntryGetParentEntry (obj,kIOServicePlane,&obj2);
        NSString *bsdName = CFBridgingRelease(IORegistryEntryCreateCFProperty(obj2,CFSTR("BSD Name"),kCFAllocatorDefault,0));
        //NSString *volID = CFBridgingRelease(IORegistryEntryCreateCFProperty(obj2,CFSTR("UUID"),0));
        NSString *groupID = CFBridgingRelease(IORegistryEntryCreateCFProperty(obj2,CFSTR("VolGroupUUID"),0));
        NSArray *roles = CFBridgingRelease(IORegistryEntryCreateCFProperty(obj2,CFSTR("Role"),0));
        if (groupID != nil && ![groupID isEqualToString:@"00000000-0000-0000-0000-000000000000"]) {
            NSLog(@"%@: %@,groupID,roles);
        }
    }
}

有了这两个信息,IOKit中的卷可以通过其BSD名称与NSURL匹配。

但是,还有另一种特殊情况:在macOS Big Sur上,根系统的设备不是常规的“ diskXsY”,而是快照设备,例如“ diskXsYsZ”。尽管IOKit代码也列出了该条目,但其条目缺少角色信息。

以下是Mac上的示例输出,其中包含Big Sur和Catalina系统,如问题所示(为​​便于阅读而略作编辑):

disk3s1s1,/,1,BigSur
disk3s5,/System/Volumes/VM,VM
disk3s3,/System/Volumes/Preboot,Preboot
disk3s6,/System/Volumes/Update,Update
disk4s1,/Volumes/Catalina - Daten,Catalina - Daten
disk4s2,/Volumes/Catalina,Catalina
disk3s2,/System/Volumes/Data,BigSur

disk4s1:   18464FE4-8321-4D36-B87A-53AC38EF6AEF,18464FE4-8321-4D36-B87A-53AC38EF6AEF,("Data")
disk3s1:   86812DBD-9252-4A2E-8887-752418DECE13,058517A6-48DD-46AB-8A78-C1F115AE6E13,("System")
disk4s2:   51DEC6AC-2D68-4B60-AE23-74BCA2C3A484,("System")
disk3s2:   058517A6-48DD-46AB-8A78-C1F115AE6E13,("Data")
disk3s1s1: C26440B0-0207-4227-A4B1-EBDD62C90D24,(null)

我已经发布了一个工作代码示例,该示例确定了所有已安装的卷及其组关系。完整的可编译代码(可以在新的Obj-C App项目的AppDelegate.m文件中替换),可以在以下位置找到:https://gist.github.com/tempelmann/80efc2eb84f0171a96822290dee7d8d9

,

以下shell命令列出了卷组:

{
    "compilerOptions": {
        "module": "CommonJS","target": "ES5","esModuleInterop": true,}
}

对于通过代码进行解析,可以附加选项以将其输出为plist,然后可以使用diskutil apfs listVolumeGroups CFPropertyListCreateWithData进行导入:

format:kCFPropertyListXMLFormat_v1_0

tweet byHoward Oakley中提供的答案)

,

使用.explode检查卷及其名称。 df = pd.DataFrame({'col_str': ['[{"a": "46","b": "3","c": "12"},{"b": "2","c": "7"}]','[{"b": "2","c": "7"},{"c": "11"}]',np.nan]}) col_str 0 [{"a": "46","c": "7"}] 1 [{"b": "2",{"c": "11"}] 2 NaN type(df.iloc[0,0]) [out]: str # fillna df.col_str = df.col_str.fillna('[]') # literal_eval df.col_str = df.col_str.apply(literal_eval) # explode df = df.explode('col_str').reset_index(drop=True) # fillna again df.col_str = df.col_str.fillna({i: {} for i in df.index}) # use json_normalize df = df.join(pd.json_normalize(df.col_str)).drop(columns=['col_str']) # display(df) a b c 0 46 3 12 1 NaN 2 7 2 NaN 2 7 3 NaN NaN 11 4 NaN NaN NaN 显示很多信息,但是您必须解析输出信息以获取卷信息,使用diskutil list将创建一个文件,以了解特定查询的可用值。