问题描述
从macOS 10.15(Catalina)开始,一个卷(如用户所见)实际上可能由多个卷组成,例如“系统”卷和“数据”卷。
我正在编写一个工具,该工具需要分别识别这些卷,因为使用searchfs
和fts_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
将创建一个文件,以了解特定查询的可用值。