【UUID的来源】
Linux系统中一个卷只有在格式化的时候才会拥有一个全局唯一ID(UUID),这个UUID将会存储于卷的superblock中,会被很多命令索引,可以参见文章《Linux获取卷或文件系统唯一标识(UUID)的四种通用方法》。
【问题】
针对一个系统中存在多个相同UUID卷的解决方案之一即是对UUID冲突的卷赋予新的UUID或者维持其原有的UUID。
Linux系统中对一个卷进行保护时,其metadata数据和非Metadata数据都将作为待保护数据被备份至后端存储设备中。那么当对一个卷的备份采用 byte-by-byte方式执行恢复操作的时,无论是恢复至原始卷还是恢复至一个新的卷,其都将和备份卷拥有相同的GUID/UUID/Volume GUID/FileSystem UUID。
上述问题对应的应用场景:假设当前Linux系统存在两个LVM卷:lvmvol-01, lvmvol-02
1. 对lvmvol-01进行全量备份,备份卷标记为:lvmvol-01-bakcup
2. 将备份卷lvmvol-01-backup恢复至卷lvmvol-02
3. 通过执行命令blkid发现:卷lvmvol-02的UUID将被改写为与lvmvol-01一样的UUID,也就是系统中同时存在两个相同UUID的卷。多数情况下,这将会引起未知错误,比如系统启动时候卷无法正常挂载。。。
【解决方法】
本文将采用命令tune2fs来更新一个卷的GUID,从而解决不同卷GUID冲突问题。显然在某些实际应用场景中这种做法未必合理!需要注意的是,此命令无法对处于挂载状态的卷进行GUID信息更新。
一般情况下,卷会被挂载到某一个目录以进行读写操作,此处假设卷lvmvol-02被挂载到mnt_point。
- 在卷lvmvol-02被覆写之前,记录其UUID;
- 在卷lvmvol-02被覆写之后,重置其UUID;
- 验证是否生效;
/*
* util.h
*/
#define MAX_BUFFER_LENGTH 160
int get_cmd_output_withoption(const char *cmd_str, char **ret_str, bool_t need_output);
/*
* util.c
*/
int
get_cmd_output_withoption(
const char *cmd_str,
char **ret_str,
bool_t need_output)
{
int ret = 0;
FILE *stream;
char buffer[MAX_BUFFER_LENGTH];
char * sub_str = NULL;
stream = popen(cmd_str, "r");
if (!stream) {
printf("Unable to spawn command: '%s'.\n", cmd_str);
return (-1);
}
if (TRUE == need_output && NULL != *ret_str) {
if (NULL != lg_fgets(buffer, MAX_BUFFER_LENGTH, stream)) {
// The LVM volume name length limits [1, 127], so we just read the first MAX_BUFFER_LENGTH data
if (NULL != strchr(buffer, '\n')) {
sub_str = strtok(buffer, "\n");
strncpy(*ret_str, sub_str, strlen(sub_str)+1);
}
}
printf("Command '%s' output: '%s'.\n", cmd_str, *ret_str);
}
ret = lg_pclose(stream);
return (ret);
}
/*
* recover.c
*/
#include <stdlib.h>
#include <stdio.h>
#include <blkid/blkid.h>
#include <util.h>
char *cmd_str = NULL;
char *volume_name = NULL;
char *original_volume_guid = NULL;
char *source_volume_guid = b6259d20-c9f1-4fb2-993c-67531525f57f";
// 1. 记录目标卷(被覆写)的UUID
cmd_str = (char *)malloc(MAX_BUFFER_LENGTH * sizeof (char));
volume_name = (char *)malloc(MAX_BUFFER_LENGTH * sizeof (char));
if (NULL == cmd_str || NULL == volume_name) {
print("Unable to allocate memory!\n");
goto out;
}
memset(cmd_str, '\0', MAX_BUFFER_LENGTH * sizeof (char));
memset(volume_name, '\0', MAX_BUFFER_LENGTH * sizeof (char));
sprintf(cmd_str, MAX_BUFFER_LENGTH, "mount | grep %s | awk \'{print $1}\'", mnt_point);
// 1.1 执行命令,获取一个挂载点对应的卷名称
if (get_cmd_output_withoption(cmd_str, &volume_name, TRUE)) {
print("Error occurs when try to execute command: '%s'.\n", cmd_str);
goto out;
}
// 1.2 通过卷名获取其原始的UUID
if (NULL != volume_name) {
original_volume_guid = blkid_get_tag_value(NULL, "UUID", volume_name);
} else {
print("The volume name is empty, please check!\n");
goto out;
}
/* 2. 卷覆写操作业务逻辑 */
........................................
........................................
........................................
// 3. 当目标卷的GUID和源卷的GUID不一样时,重置被覆写卷的UUID,若重置失败则退出
if (NULL != original_volume_guid && NULL != volume_name && strcmp(source_volume_guid, original_volume_guid)) {
memset(cmd_str, '\0', MAX_BUFFER_LENGTH * sizeof (char));
// 命令tune2fs无法应用于处于挂载状态的卷信息的更新
sprintf(cmd_str, MAX_BUFFER_LENGTH, "tune2fs -U %s %s", original_volume_guid, volume_name);
// 3.1 执行命令,重置目标卷的UUID
if (get_cmd_output_withoption(cmd_str, NULL, FALSE)) {
print("Error occurs when try to execute command: '%s'.\n", cmd_str);
goto out;
}
// 资源释放
out:
if (NULL != cmd_str) {
free(cmd_str);
cmd_str = NULL:
}
if (NULL != volume_name) {
free(volume_name);
volume_name = NULL:
}
....
....
....
【验证】
关于如何验证上述代码是否生效,可以参见文章《Linux获取卷或文件系统唯一标识(UUID)的四种通用方法》中方法,任选一种即可。
【参考资料】
http://ftp.ntu.edu.tw/linux/utils/util-linux/v2.26/libblkid-docs/libblkid-Search-and-iterate.html
https://www.techrepublic.com/blog/linux-and-open-source/drive-and-partition-backups-with-dd/