xref: /haiku/src/add-ons/kernel/file_systems/iso9660/iso9660_identify.cpp (revision f47bff088097e3f4c04e95fbdb2383caa65ff996)
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 
59 static const char *kISO9660Signature = "CD001";
60 static const uint32 kVolumeDescriptorLength = 2048;
61 #define ISO9660_VOLUME_IDENTIFIER_LENGTH 32
62 #define ISO9660_ESCAPE_SEQUENCE_LENGTH 32
63 
64 //! Volume descriptor types
65 typedef 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 */
76 typedef 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 
83 typedef 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 
107 typedef 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 
118 static 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 */
iso9660_info()130 iso9660_info::iso9660_info()
131 	:
132 	iso9660_name(NULL),
133 	joliet_name(NULL)
134 {
135 }
136 
137 
~iso9660_info()138 iso9660_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 */
147 bool
IsValid()148 iso9660_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 */
159 void
SetISO9660Name(const char * name,uint32 length)160 iso9660_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 */
171 void
SetJolietName(const char * name,uint32 length)172 iso9660_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 */
184 const char*
PreferredName()185 iso9660_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 */
197 void
_SetString(char ** string,const char * newString,uint32 newLength)198 iso9660_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 */
226 static void
unicode_to_utf8(uint32 c,char ** out)227 unicode_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 
250 static const char*
descriptor_type_to_string(iso9660_descriptor_type type)251 descriptor_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 
270 static void
dump_common_descriptor(iso9660_common_descriptor * common,const char * indent,bool printHeader)271 dump_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 
288 static void
dump_primary_descriptor(iso9660_volume_descriptor * primary,const char * indent,bool printHeader)289 dump_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 
315 static void
dump_supplementary_descriptor(iso9660_volume_descriptor * supplementary,const char * indent,bool printHeader)316 dump_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 
333 static void
dump_directory_record(iso9660_directory_record * record,const char * indent)334 dump_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 
347 static status_t
check_common_descriptor(iso9660_common_descriptor * common)348 check_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 */
368 status_t
iso9660_fs_identify(int deviceFD,iso9660_info * info)369 iso9660_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