1/*
2 * Copyright 2007-2009, Axel Dörfler, axeld@pinc-software.de.
3 * Copyright 2002, Tyler Dauwalder.
4 *
5 * This file may be used under the terms of the MIT License.
6 */
7
8/*!
9	<h5>iso9660</h5>
10	The standard to which this module is written is ECMA-119 second
11	edition, a freely available iso9660 equivalent.
12
13	<h5>Joliet</h5>
14	Joliet support comes courtesy of the following document:
15
16	http://www-plateau.cs.berkeley.edu/people/chaffee/jolspec.htm
17
18	As specified there, the existence of any of the following escape
19	sequences in a supplementary volume descriptor's "escape sequences"
20	field denotes a Joliet volume descriptor using unicode ucs-2
21	character encoding (2-byte characters, big-endian):
22
23	- UCS-2 Level 1: 0x252F40 == "%/@"
24	- UCS-2 Level 2: 0x252F43 == "%/C"
25	- UCS-2 Level 3: 0x252F45 == "%/E"
26
27	The following UCS-2 characters are considered illegal (we allow them,
28	printing out a warning if encountered):
29
30	- All values between 0x0000 and 0x001f inclusive == control chars
31	- 0x002A == '*'
32	- 0x002F == '/'
33	- 0x003A == ':'
34	- 0x003B == ';'
35	- 0x003F == '?'
36	- 0x005C == '\'
37*/
38
39#include "iso9660_identify.h"
40
41#ifndef FS_SHELL
42#	include <errno.h>
43#	include <stdlib.h>
44#	include <string.h>
45#	include <unistd.h>
46#	include <stdio.h>
47
48#	include <ByteOrder.h>
49#	include <fs_info.h>
50#	include <KernelExport.h>
51#endif
52
53#include "iso9660.h"
54
55//#define TRACE(x) ;
56#define TRACE(x) dprintf x
57
58
59static const char *kISO9660Signature = "CD001";
60static const uint32 kVolumeDescriptorLength = 2048;
61#define ISO9660_VOLUME_IDENTIFIER_LENGTH 32
62#define ISO9660_ESCAPE_SEQUENCE_LENGTH 32
63
64//! Volume descriptor types
65typedef enum {
66	ISO9660VD_BOOT,
67	ISO9660VD_PRIMARY,
68	ISO9660VD_SUPPLEMENTARY,
69	ISO9660VD_PARTITION,
70	ISO9660VD_TERMINATOR = 255
71} iso9660_descriptor_type;
72
73/*! \brief The portion of the volume descriptor common to all
74    descriptor types.
75*/
76typedef struct iso9660_common_descriptor {
77	uint8	type;
78	char	standard_identifier[5];	// should be 'CD001'
79	uint8	version;
80	// Remaining bytes are unused
81} __attribute__((packed)) iso9660_common_volume_descriptor;
82
83typedef struct iso9660_volume_descriptor {
84	iso9660_common_descriptor common;
85	uint8	flags;
86	char	system_identifier[32];
87	char	identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH];
88	uint8	_reserved0[8];
89	uint32	size;
90	uint32	size_big_endian;
91	char	escape_sequences[ISO9660_ESCAPE_SEQUENCE_LENGTH];
92				// unused on primary descriptor
93	uint16	set_size;
94	uint16	set_size_big_endian;
95	uint16	sequence_number;
96	uint16	sequence_number_big_endian;
97	uint16	logical_block_size;
98	uint16	logical_block_size_big_endian;
99	uint32	path_table_size;
100	uint32	path_table_size_big_endian;
101	uint32	_reserved1[4];
102	uint8	root_directory_record[34];
103	char	set_identifier[28];
104	// Remaining bytes are disinteresting to us
105} __attribute__((packed)) iso9660_volume_descriptor;
106
107typedef struct iso9660_directory_record {
108	uint8	length;
109	uint8	extended_attribute_record_length;
110	uint32	location;
111	uint32	location_big_endian;
112	uint32	data_length;
113	uint8	_reserved[14];
114	uint16	volume_space;
115} __attribute__((packed)) iso9660_directory_record;
116
117
118static void dump_directory_record(iso9660_directory_record *record,
119	const char *indent);
120
121
122//	#pragma mark -
123
124
125/*! \brief Creates a new iso9660_info struct with empty volume names.
126
127	\note Use the applicable set_XYZ_volume_name() functions rather than
128	messing with the volume name data members directly.
129*/
130iso9660_info::iso9660_info()
131	:
132	iso9660_name(NULL),
133	joliet_name(NULL)
134{
135}
136
137
138iso9660_info::~iso9660_info()
139{
140	free(iso9660_name);
141	free(joliet_name);
142}
143
144
145/*! \brief Returns true if a valid volume name exists.
146*/
147bool
148iso9660_info::IsValid()
149{
150	return iso9660_name != NULL || joliet_name != NULL;
151}
152
153
154/*! \brief Sets the iso9660 volume name.
155
156	\param name UTF-8 string containing the name.
157	\param length The length (in bytes) of the string.
158*/
159void
160iso9660_info::SetISO9660Name(const char *name, uint32 length)
161{
162	_SetString(&iso9660_name, name, length);
163}
164
165
166/*! \brief Sets the Joliet volume name.
167
168	\param name UTF-8 string containing the name.
169	\param length The length (in bytes) of the string.
170*/
171void
172iso9660_info::SetJolietName(const char *name, uint32 length)
173{
174	_SetString(&joliet_name, name, length);
175}
176
177
178/*! \brief Returns the volume name of highest precedence.
179
180	Currently, the ordering is (decreasingly):
181	- Joliet
182	- iso9660
183*/
184const char*
185iso9660_info::PreferredName()
186{
187	if (joliet_name)
188		return joliet_name;
189
190	return iso9660_name;
191}
192
193
194/*! \brief Copies the given string into the old string, managing memory
195	deallocation and allocation as necessary.
196*/
197void
198iso9660_info::_SetString(char **string, const char *newString,
199	uint32 newLength)
200{
201	if (string == NULL)
202		return;
203
204	TRACE(("iso9660_info::set_string(%p ('%s'), '%s', %u)\n", string,
205		*string, newString, (unsigned)newLength));
206
207	char *&oldString = *string;
208	free(oldString);
209
210	if (newString) {
211		oldString = (char*)malloc(newLength + 1);
212		if (oldString != NULL) {
213			memcpy(oldString, newString, newLength);
214			oldString[newLength] = '\0';
215		}
216	} else
217		oldString = NULL;
218}
219
220
221//	#pragma mark - C functions
222
223
224/*! \brief Converts the given unicode character to utf8.
225*/
226static void
227unicode_to_utf8(uint32 c, char **out)
228{
229	char *s = *out;
230
231	if (c < 0x80)
232		*(s++) = c;
233	else if (c < 0x800) {
234		*(s++) = 0xc0 | (c >> 6);
235		*(s++) = 0x80 | (c & 0x3f);
236	} else if (c < 0x10000) {
237		*(s++) = 0xe0 | (c >> 12);
238		*(s++) = 0x80 | ((c >> 6) & 0x3f);
239		*(s++) = 0x80 | (c & 0x3f);
240	} else if (c <= 0x10ffff) {
241		*(s++) = 0xf0 | (c >> 18);
242		*(s++) = 0x80 | ((c >> 12) & 0x3f);
243		*(s++) = 0x80 | ((c >> 6) & 0x3f);
244		*(s++) = 0x80 | (c & 0x3f);
245	}
246	*out = s;
247}
248
249
250static const char*
251descriptor_type_to_string(iso9660_descriptor_type type)
252{
253	switch (type) {
254		case ISO9660VD_BOOT:
255			return "boot";
256		case ISO9660VD_PRIMARY:
257			return "primary";
258		case ISO9660VD_SUPPLEMENTARY:
259			return "supplementary";
260		case ISO9660VD_PARTITION:
261			return "partiton";
262		case ISO9660VD_TERMINATOR:
263			return "terminator";
264		default:
265			return "invalid";
266	}
267}
268
269
270static void
271dump_common_descriptor(iso9660_common_descriptor *common,
272	const char *indent, bool printHeader)
273{
274	if (printHeader)
275		TRACE(("%siso9660_common_descriptor:\n", indent));
276
277	TRACE(("%s  volume descriptor type: %d (%s)\n", indent,
278		common->type, descriptor_type_to_string(
279			(iso9660_descriptor_type)common->type)));
280	TRACE(("%s  standard identifier:    %.5s (%s)\n", indent,
281		common->standard_identifier,
282		strncmp(common->standard_identifier, kISO9660Signature, 5) == 0
283			? "valid" : "INVALID"));
284	TRACE(("%s  version:                %d\n", indent, common->version));
285}
286
287
288static void
289dump_primary_descriptor(iso9660_volume_descriptor *primary,
290	const char *indent, bool printHeader)
291{
292	if (printHeader)
293		TRACE(("%siso9660_primary_descriptor:\n", indent));
294
295	dump_common_descriptor(&primary->common, indent, false);
296	TRACE(("%s  identifier:             '%.32s'\n", indent,
297		primary->identifier));
298	TRACE(("%s  size:                   %d\n", indent,
299		(int)B_LENDIAN_TO_HOST_INT32(primary->size)));
300	TRACE(("%s  set size:               %d\n", indent,
301		(int)B_LENDIAN_TO_HOST_INT32(primary->set_size)));
302	TRACE(("%s  sequence number:        %d\n", indent,
303		(int)B_LENDIAN_TO_HOST_INT32(primary->sequence_number)));
304	TRACE(("%s  logical block size:     %d\n", indent,
305		(int)B_LENDIAN_TO_HOST_INT32(primary->logical_block_size)));
306	TRACE(("%s  path table size:        %d\n", indent,
307		(int)B_LENDIAN_TO_HOST_INT32(primary->path_table_size)));
308	TRACE(("%s  set identifier:         %.28s\n", indent,
309		primary->set_identifier));
310	dump_directory_record((iso9660_directory_record*)
311		primary->root_directory_record, indent);
312}
313
314
315static void
316dump_supplementary_descriptor(iso9660_volume_descriptor *supplementary,
317	const char *indent, bool printHeader)
318{
319	if (printHeader)
320		TRACE(("%siso9660_supplementary_descriptor:\n", indent));
321
322	dump_primary_descriptor(supplementary, indent, false);
323	TRACE(("%s  escape sequences:      ", indent));
324	for (int i = 0; i < ISO9660_ESCAPE_SEQUENCE_LENGTH; i++) {
325		TRACE((" %2x", supplementary->escape_sequences[i]));
326		if (i == ISO9660_ESCAPE_SEQUENCE_LENGTH / 2 - 1)
327			TRACE(("\n                          "));
328	}
329	TRACE(("\n"));
330}
331
332
333static void
334dump_directory_record(iso9660_directory_record *record, const char *indent)
335{
336	TRACE(("%s  root directory record:\n", indent));
337	TRACE(("%s    length:               %d\n", indent, record->length));
338	TRACE(("%s    location:             %d\n", indent,
339		(int)B_LENDIAN_TO_HOST_INT32(record->location)));
340	TRACE(("%s    data length:          %d\n", indent,
341		(int)B_LENDIAN_TO_HOST_INT32(record->data_length)));
342	TRACE(("%s    volume space:         %d\n", indent,
343		B_LENDIAN_TO_HOST_INT16(record->volume_space)));
344}
345
346
347static status_t
348check_common_descriptor(iso9660_common_descriptor *common)
349{
350	if (common == NULL)
351		return B_BAD_VALUE;
352
353	return strncmp(common->standard_identifier, kISO9660Signature, 5) == 0
354		? B_OK : B_BAD_DATA;
355}
356
357
358//	#pragma mark - Public functions
359
360
361// iso9660_fs_identify
362/*! \brief Returns true if the given partition is a valid iso9660 partition.
363
364	See fs_identify_hook() for more information.
365
366	\todo Fill in partitionInfo->mounted_at with something useful.
367*/
368status_t
369iso9660_fs_identify(int deviceFD, iso9660_info *info)
370{
371	char buffer[ISO_PVD_SIZE];
372	bool exit = false;
373	bool found = false;
374	status_t error = B_OK;
375
376	TRACE(("identify(%d, %p)\n", deviceFD, info));
377	off_t offset = 0x8000;
378
379	// Read through the volume descriptors looking for a primary descriptor.
380	// If for some reason there are more than one primary descriptor, the
381	// volume name from the last encountered descriptor will be used.
382	while (!error && !exit) {// && count++ < 10) {
383		iso9660_common_descriptor *common = NULL;
384
385		// Read the block containing the current descriptor
386		error = read_pos(deviceFD, offset, (void *)&buffer, ISO_PVD_SIZE);
387		offset += ISO_PVD_SIZE;
388		if (error < ISO_PVD_SIZE)
389			break;
390
391		common = (iso9660_common_descriptor*)buffer;
392		error = check_common_descriptor(common);
393		if (error < B_OK)
394			break;
395
396//		dump_common_descriptor(common, "", true);
397
398		// Handle each type of descriptor appropriately
399		TRACE(("found %s descriptor\n", descriptor_type_to_string(
400			(iso9660_descriptor_type)common->type)));
401		found = true;
402
403		switch (common->type) {
404			case ISO9660VD_BOOT:
405				break;
406
407			case ISO9660VD_PRIMARY:
408			{
409				iso9660_volume_descriptor *primary
410					= (iso9660_volume_descriptor*)buffer;
411				int i;
412
413				dump_primary_descriptor(primary, "  ", true);
414
415				// Cut off any trailing spaces from the volume id. Note
416				// that this allows for spaces INSIDE the volume id, even
417				// though that's not technically allowed by the standard;
418				// this was necessary to support certain RedHat 6.2 CD-ROMs
419				// from a certain Linux company who shall remain unnamed. ;-)
420				for (i = ISO9660_VOLUME_IDENTIFIER_LENGTH - 1; i >= 0;
421						i--) {
422					if (primary->identifier[i] != 0x20)
423						break;
424				}
425
426				// Give a holler if the iso9660 name is already set
427				if (info->iso9660_name) {
428					char name[ISO9660_VOLUME_IDENTIFIER_LENGTH + 1];
429					strlcpy(name, primary->identifier, i + 1);
430					TRACE(("duplicate iso9660 volume name found, using "
431						"latter (`%s') instead of former (`%s')\n", name,
432						info->iso9660_name));
433				}
434
435				info->SetISO9660Name(primary->identifier, i + 1);
436				info->max_blocks = B_LENDIAN_TO_HOST_INT32(primary->set_size);
437				break;
438			}
439
440			case ISO9660VD_SUPPLEMENTARY:
441			{
442				iso9660_volume_descriptor *supplementary
443					= (iso9660_volume_descriptor*)buffer;
444				dump_supplementary_descriptor(supplementary, "  ", true);
445
446				// Copy and null terminate the escape sequences
447				char escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH + 1];
448				strlcpy(escapes, supplementary->escape_sequences,
449					ISO9660_ESCAPE_SEQUENCE_LENGTH + 1);
450
451				// Check for a Joliet VD
452				if (strstr(escapes, "%/@") || strstr(escapes, "%/C")
453					|| strstr(escapes, "%/E")) {
454					char name[(ISO9660_VOLUME_IDENTIFIER_LENGTH * 3 / 2) + 1];
455						// Since we're dealing with 16-bit Unicode, each
456						// UTF-8 sequence will be at most 3 bytes long.
457					char *pos = name;
458					uint16 ch;
459
460					// Walk thru the unicode volume name, converting to utf8 as we go.
461					for (int i = 0; (ch = B_BENDIAN_TO_HOST_INT16(
462								((uint16*)supplementary->identifier)[i]))
463							&& i < ISO9660_VOLUME_IDENTIFIER_LENGTH; i++) {
464						// Give a warning if the character is technically
465						// illegal
466						if (ch <= 0x001F || ch == '*' || ch == '/'
467						    || ch == ':' || ch == ';'
468						    || ch == '?' || ch == '\\') {
469							TRACE(("warning: illegal Joliet character "
470								"found: 0%4x\n", ch));
471						}
472
473						// Convert to utf-8
474						unicode_to_utf8(ch, &pos);
475					}
476					pos[0] = '\0';
477
478					// Give a holler if the joliet name is already set
479					if (info->joliet_name) {
480						TRACE(("duplicate joliet volume name found, using "
481							"latter (`%s') instead of former (`%s')\n",
482							name, info->joliet_name));
483					}
484
485					info->SetJolietName(name, pos - name);
486				}
487				break;
488			}
489
490			case ISO9660VD_PARTITION:
491				break;
492
493			case ISO9660VD_TERMINATOR:
494				exit = true;
495				break;
496
497			default:
498				break;
499		}
500	}
501
502	return found ? B_OK : error;
503}
504
505