1/*
2 * volume_id - reads filesystem label and uuid
3 *
4 * Copyright (C) 2004 Kay Sievers <kay.sievers@vrfy.org>
5 *
6 *	This program is free software; you can redistribute it and/or modify it
7 *	under the terms of the GNU General Public License as published by the
8 *	Free Software Foundation version 2 of the License.
9 */
10
11#ifndef _GNU_SOURCE
12#define _GNU_SOURCE 1
13#endif
14
15#ifdef HAVE_CONFIG_H
16#  include <config.h>
17#endif
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <unistd.h>
22#include <string.h>
23#include <errno.h>
24#include <ctype.h>
25
26#include "libvolume_id.h"
27#include "util.h"
28
29struct hfs_finder_info{
30	uint32_t	boot_folder;
31	uint32_t	start_app;
32	uint32_t	open_folder;
33	uint32_t	os9_folder;
34	uint32_t	reserved;
35	uint32_t	osx_folder;
36	uint8_t		id[8];
37} PACKED;
38
39static struct hfs_mdb {
40	uint8_t		signature[2];
41	uint32_t	cr_date;
42	uint32_t	ls_Mod;
43	uint16_t	atrb;
44	uint16_t	nm_fls;
45	uint16_t	vbm_st;
46	uint16_t	alloc_ptr;
47	uint16_t	nm_al_blks;
48	uint32_t	al_blk_size;
49	uint32_t	clp_size;
50	uint16_t	al_bl_st;
51	uint32_t	nxt_cnid;
52	uint16_t	free_bks;
53	uint8_t		label_len;
54	uint8_t		label[27];
55	uint32_t	vol_bkup;
56	uint16_t	vol_seq_num;
57	uint32_t	wr_cnt;
58	uint32_t	xt_clump_size;
59	uint32_t	ct_clump_size;
60	uint16_t	num_root_dirs;
61	uint32_t	file_count;
62	uint32_t	dir_count;
63	struct hfs_finder_info finder_info;
64	uint8_t		embed_sig[2];
65	uint16_t	embed_startblock;
66	uint16_t	embed_blockcount;
67} PACKED *hfs;
68
69struct hfsplus_bnode_descriptor {
70	uint32_t	next;
71	uint32_t	prev;
72	uint8_t		type;
73	uint8_t		height;
74	uint16_t	num_recs;
75	uint16_t	reserved;
76} PACKED;
77
78struct hfsplus_bheader_record {
79	uint16_t	depth;
80	uint32_t	root;
81	uint32_t	leaf_count;
82	uint32_t	leaf_head;
83	uint32_t	leaf_tail;
84	uint16_t	node_size;
85} PACKED;
86
87struct hfsplus_catalog_key {
88	uint16_t	key_len;
89	uint32_t	parent_id;
90	uint16_t	unicode_len;
91	uint8_t		unicode[255 * 2];
92} PACKED;
93
94struct hfsplus_extent {
95	uint32_t	start_block;
96	uint32_t	block_count;
97} PACKED;
98
99#define HFSPLUS_EXTENT_COUNT		8
100struct hfsplus_fork {
101	uint64_t	total_size;
102	uint32_t	clump_size;
103	uint32_t	total_blocks;
104	struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
105} PACKED;
106
107static struct hfsplus_vol_header {
108	uint8_t		signature[2];
109	uint16_t	version;
110	uint32_t	attributes;
111	uint32_t	last_mount_vers;
112	uint32_t	reserved;
113	uint32_t	create_date;
114	uint32_t	modify_date;
115	uint32_t	backup_date;
116	uint32_t	checked_date;
117	uint32_t	file_count;
118	uint32_t	folder_count;
119	uint32_t	blocksize;
120	uint32_t	total_blocks;
121	uint32_t	free_blocks;
122	uint32_t	next_alloc;
123	uint32_t	rsrc_clump_sz;
124	uint32_t	data_clump_sz;
125	uint32_t	next_cnid;
126	uint32_t	write_count;
127	uint64_t	encodings_bmp;
128	struct hfs_finder_info finder_info;
129	struct hfsplus_fork alloc_file;
130	struct hfsplus_fork ext_file;
131	struct hfsplus_fork cat_file;
132	struct hfsplus_fork attr_file;
133	struct hfsplus_fork start_file;
134} PACKED *hfsplus;
135
136#define HFS_SUPERBLOCK_OFFSET		0x400
137#define HFS_NODE_LEAF			0xff
138#define HFSPLUS_POR_CNID		1
139
140static void hfsid_set_uuid(struct volume_id *id, const uint8_t *hfs_id)
141{
142
143	volume_id_set_uuid(id, hfs_id, 0, UUID_64BIT_BE);
144}
145
146int volume_id_probe_hfs_hfsplus(struct volume_id *id, uint64_t off, uint64_t size)
147{
148	unsigned int blocksize;
149	unsigned int cat_block;
150	unsigned int ext_block_start;
151	unsigned int ext_block_count;
152	int ext;
153	unsigned int leaf_node_head;
154	unsigned int leaf_node_count;
155	unsigned int leaf_node_size;
156	unsigned int leaf_block;
157	uint64_t leaf_off;
158	unsigned int alloc_block_size;
159	unsigned int alloc_first_block;
160	unsigned int embed_first_block;
161	unsigned int record_count;
162	struct hfsplus_bnode_descriptor *descr;
163	struct hfsplus_bheader_record *bnode;
164	struct hfsplus_catalog_key *key;
165	unsigned int label_len;
166	struct hfsplus_extent extents[HFSPLUS_EXTENT_COUNT];
167	const uint8_t *buf;
168
169	info("probing at offset 0x%llx", (unsigned long long) off);
170
171	buf = volume_id_get_buffer(id, off + HFS_SUPERBLOCK_OFFSET, 0x200);
172	if (buf == NULL)
173                return -1;
174
175	hfs = (struct hfs_mdb *) buf;
176	if (memcmp(hfs->signature, "BD", 2) != 0)
177		goto checkplus;
178
179	/* it may be just a hfs wrapper for hfs+ */
180	if (memcmp(hfs->embed_sig, "H+", 2) == 0) {
181		alloc_block_size = be32_to_cpu(hfs->al_blk_size);
182		dbg("alloc_block_size 0x%x", alloc_block_size);
183
184		alloc_first_block = be16_to_cpu(hfs->al_bl_st);
185		dbg("alloc_first_block 0x%x", alloc_first_block);
186
187		embed_first_block = be16_to_cpu(hfs->embed_startblock);
188		dbg("embed_first_block 0x%x", embed_first_block);
189
190		off += (alloc_first_block * 512) +
191		       (embed_first_block * alloc_block_size);
192		dbg("hfs wrapped hfs+ found at offset 0x%llx", (unsigned long long) off);
193
194		buf = volume_id_get_buffer(id, off + HFS_SUPERBLOCK_OFFSET, 0x200);
195		if (buf == NULL)
196			return -1;
197		goto checkplus;
198	}
199
200	if (hfs->label_len > 0 && hfs->label_len < 28) {
201		volume_id_set_label_raw(id, hfs->label, hfs->label_len);
202		volume_id_set_label_string(id, hfs->label, hfs->label_len) ;
203	}
204
205	hfsid_set_uuid(id, hfs->finder_info.id);
206
207	volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
208	id->type = "hfs";
209
210	return 0;
211
212checkplus:
213	hfsplus = (struct hfsplus_vol_header *) buf;
214	if (memcmp(hfsplus->signature, "H+", 2) == 0)
215		goto hfsplus;
216	if (memcmp(hfsplus->signature, "HX", 2) == 0)
217		goto hfsplus;
218	return -1;
219
220hfsplus:
221	hfsid_set_uuid(id, hfsplus->finder_info.id);
222
223	blocksize = be32_to_cpu(hfsplus->blocksize);
224	dbg("blocksize %u", blocksize);
225
226	memcpy(extents, hfsplus->cat_file.extents, sizeof(extents));
227	cat_block = be32_to_cpu(extents[0].start_block);
228	dbg("catalog start block 0x%x", cat_block);
229
230	buf = volume_id_get_buffer(id, off + (cat_block * blocksize), 0x2000);
231	if (buf == NULL)
232		goto found;
233
234	bnode = (struct hfsplus_bheader_record *)
235		&buf[sizeof(struct hfsplus_bnode_descriptor)];
236
237	leaf_node_head = be32_to_cpu(bnode->leaf_head);
238	dbg("catalog leaf node 0x%x", leaf_node_head);
239
240	leaf_node_size = be16_to_cpu(bnode->node_size);
241	dbg("leaf node size 0x%x", leaf_node_size);
242
243	leaf_node_count = be32_to_cpu(bnode->leaf_count);
244	dbg("leaf node count 0x%x", leaf_node_count);
245	if (leaf_node_count == 0)
246		goto found;
247
248	leaf_block = (leaf_node_head * leaf_node_size) / blocksize;
249
250	/* get physical location */
251	for (ext = 0; ext < HFSPLUS_EXTENT_COUNT; ext++) {
252		ext_block_start = be32_to_cpu(extents[ext].start_block);
253		ext_block_count = be32_to_cpu(extents[ext].block_count);
254		dbg("extent start block 0x%x, count 0x%x", ext_block_start, ext_block_count);
255
256		if (ext_block_count == 0)
257			goto found;
258
259		/* this is our extent */
260		if (leaf_block < ext_block_count)
261			break;
262
263		leaf_block -= ext_block_count;
264	}
265	if (ext == HFSPLUS_EXTENT_COUNT)
266		goto found;
267	dbg("found block in extent %i", ext);
268
269	leaf_off = (ext_block_start + leaf_block) * blocksize;
270
271	buf = volume_id_get_buffer(id, off + leaf_off, leaf_node_size);
272	if (buf == NULL)
273		goto found;
274
275	descr = (struct hfsplus_bnode_descriptor *) buf;
276	dbg("descriptor type 0x%x", descr->type);
277
278	record_count = be16_to_cpu(descr->num_recs);
279	dbg("number of records %u", record_count);
280	if (record_count == 0)
281		goto found;
282
283	if (descr->type != HFS_NODE_LEAF)
284		goto found;
285
286	key = (struct hfsplus_catalog_key *)
287		&buf[sizeof(struct hfsplus_bnode_descriptor)];
288
289	dbg("parent id 0x%x", be32_to_cpu(key->parent_id));
290	if (be32_to_cpu(key->parent_id) != HFSPLUS_POR_CNID)
291		goto found;
292
293	label_len = be16_to_cpu(key->unicode_len) * 2;
294	dbg("label unicode16 len %i", label_len);
295	volume_id_set_label_raw(id, key->unicode, label_len);
296	volume_id_set_label_unicode16(id, key->unicode, BE, label_len);
297
298found:
299	volume_id_set_usage(id, VOLUME_ID_FILESYSTEM);
300	id->type = "hfsplus";
301
302	return 0;
303}
304