summaryrefslogtreecommitdiff
path: root/extras/volume_id/lib/linux_raid.c
blob: f7d1af4fe73379b71b67906f0ab5a575ca52a40b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
 * volume_id - reads filesystem label and uuid
 *
 * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <byteswap.h>

#include "libvolume_id.h"
#include "libvolume_id-private.h"

struct mdp0_super_block {
	uint32_t	md_magic;
	uint32_t	major_version;
	uint32_t	minor_version;
	uint32_t	patch_version;
	uint32_t	gvalid_words;
	uint32_t	set_uuid0;
	uint32_t	ctime;
	uint32_t	level;
	uint32_t	size;
	uint32_t	nr_disks;
	uint32_t	raid_disks;
	uint32_t	md_minor;
	uint32_t	not_persistent;
	uint32_t	set_uuid1;
	uint32_t	set_uuid2;
	uint32_t	set_uuid3;
} PACKED;

struct mdp1_super_block {
	uint32_t	magic;
	uint32_t	major_version;
	uint32_t	feature_map;
	uint32_t	pad0;
	uint8_t		set_uuid[16];
	uint8_t		set_name[32];
} PACKED;

#define MD_RESERVED_BYTES		0x10000
#define MD_SB_MAGIC			0xa92b4efc

static int volume_id_probe_linux_raid0(struct volume_id *id, uint64_t off, uint64_t size)
{
	const uint8_t *buf;
	struct mdp0_super_block *mdp0;
	union {
		uint32_t ints[4];
		uint8_t bytes[16];
	} uuid;

	info("probing at offset 0x%llx, size 0x%llx\n",
	    (unsigned long long) off, (unsigned long long) size);
	if (size < 0x10000)
		return -1;

	buf = volume_id_get_buffer(id, off, 0x800);
	if (buf == NULL)
		return -1;
	mdp0 = (struct mdp0_super_block *) buf;

	if (le32_to_cpu(mdp0->md_magic) == MD_SB_MAGIC) {
		uuid.ints[0] = bswap_32(mdp0->set_uuid0);
		if (le32_to_cpu(mdp0->minor_version >= 90)) {
			uuid.ints[1] = bswap_32(mdp0->set_uuid1);
			uuid.ints[2] = bswap_32(mdp0->set_uuid2);
			uuid.ints[3] = bswap_32(mdp0->set_uuid3);
		} else {
			uuid.ints[1] = 0;
			uuid.ints[2] = 0;
			uuid.ints[3] = 0;
		}
		volume_id_set_uuid(id, uuid.bytes, 0, UUID_MD);
		snprintf(id->type_version, sizeof(id->type_version)-1, "%u.%u.%u",
			 le32_to_cpu(mdp0->major_version),
			 le32_to_cpu(mdp0->minor_version),
			 le32_to_cpu(mdp0->patch_version));
	} else if (be32_to_cpu(mdp0->md_magic) == MD_SB_MAGIC) {
		uuid.ints[0] = mdp0->set_uuid0;
		if (be32_to_cpu(mdp0->minor_version >= 90)) {
			uuid.ints[1] = mdp0->set_uuid1;
			uuid.ints[2] = mdp0->set_uuid2;
			uuid.ints[3] = mdp0->set_uuid3;
		} else {
			uuid.ints[1] = 0;
			uuid.ints[2] = 0;
			uuid.ints[3] = 0;
		}
		volume_id_set_uuid(id, uuid.bytes, 0, UUID_MD);
		snprintf(id->type_version, sizeof(id->type_version)-1, "%u.%u.%u",
			 be32_to_cpu(mdp0->major_version),
			 be32_to_cpu(mdp0->minor_version),
			 be32_to_cpu(mdp0->patch_version));
	} else
		return -1;

	volume_id_set_usage(id, VOLUME_ID_RAID);
	id->type = "linux_raid_member";
	return 0;
}

static int volume_id_probe_linux_raid1(struct volume_id *id, uint64_t off, uint64_t size)
{
	const uint8_t *buf;
	struct mdp1_super_block *mdp1;

	info("probing at offset 0x%llx, size 0x%llx\n",
	    (unsigned long long) off, (unsigned long long) size);

	buf = volume_id_get_buffer(id, off, 0x800);
	if (buf == NULL)
		return -1;
	mdp1 = (struct mdp1_super_block *) buf;

	if (le32_to_cpu(mdp1->magic) != MD_SB_MAGIC)
		return -1;

	if (le32_to_cpu(mdp1->major_version) != 1)
		return -1;

	volume_id_set_uuid(id, mdp1->set_uuid, 0, UUID_MD);
	volume_id_set_label_raw(id, mdp1->set_name, 32);
	volume_id_set_label_string(id, mdp1->set_name, 32);
	volume_id_set_usage(id, VOLUME_ID_RAID);
	id->type = "linux_raid_member";
	return 0;
}

int volume_id_probe_linux_raid(struct volume_id *id, uint64_t off, uint64_t size)
{
	uint64_t sboff;

	if (size > MD_RESERVED_BYTES) {
		/* version 0 at the end of the device */
		sboff = (size & ~(MD_RESERVED_BYTES - 1)) - MD_RESERVED_BYTES;
		if (volume_id_probe_linux_raid0(id, off + sboff, size) == 0)
			return 0;

		/* version 1.0 at the end of the device */
		sboff = (size & ~(0x1000 - 1)) - 0x2000;
		if (volume_id_probe_linux_raid1(id, off + sboff, size) == 0) {
			strcpy(id->type_version, "1.0");
			return 0;
		}
	}

	/* version 1.1 at the start of the device */
	if (volume_id_probe_linux_raid1(id, off, size) == 0) {
		strcpy(id->type_version, "1.1");
		return 0;
	}

	/* version 1.2 at 4k offset from the start */
	if (volume_id_probe_linux_raid1(id, off + 0x1000, size) == 0) {
		strcpy(id->type_version, "1.2");
		return 0;
	}

	return -1;
}