xref: /haiku/src/add-ons/kernel/file_systems/iso9660/iso9660.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
1 /*
2  * Copyright 2002, Tyler Dauwalder.
3  * This file may be used under the terms of the MIT License.
4  */
5 /*!
6 	\file iso9660.cpp
7 	\brief disk_scanner filesystem module for iso9660 CD-ROM filesystems
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 <errno.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #include <stdio.h>
44 
45 #include <ByteOrder.h>
46 #include <fs_info.h>
47 #include <KernelExport.h>
48 
49 #include "iso.h"
50 #include "iso9660.h"
51 
52 //#define TRACE(x) ;
53 #define TRACE(x) dprintf x
54 
55 // misc constants
56 static const char *kISO9660Signature = "CD001";
57 static const uint32 kVolumeDescriptorLength = 2048;
58 #define ISO9660_VOLUME_IDENTIFIER_LENGTH 32
59 #define ISO9660_ESCAPE_SEQUENCE_LENGTH 32
60 
61 //! Volume descriptor types
62 typedef enum {
63 	ISO9660VD_BOOT,
64 	ISO9660VD_PRIMARY,
65 	ISO9660VD_SUPPLEMENTARY,
66 	ISO9660VD_PARTITION,
67 	ISO9660VD_TERMINATOR = 255
68 } iso9660_volume_descriptor_type;
69 
70 /*! \brief The portion of the volume descriptor common to all
71     descriptor types.
72 */
73 typedef struct iso9660_common_volume_descriptor {
74 	uchar volume_descriptor_type;
75 	char standard_identifier[5];	// should be 'CD001'
76 	uchar volume_descriptor_version;
77 	// Remaining bytes are unused
78 } __attribute__((packed)) iso9660_common_volume_descriptor;
79 
80 /*! \brief Primary volume descriptor
81 */
82 typedef struct iso9660_primary_volume_descriptor {
83 	iso9660_common_volume_descriptor info;
84 	uchar volume_flags;
85 	char system_identifier[32];
86 	char volume_identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH];
87 	uchar unused01[8];
88 	uint32 volume_space_size_little_endian;
89 	uint32 volume_space_size_big_endian;
90 	uchar unused02[ISO9660_ESCAPE_SEQUENCE_LENGTH];
91 	uint16 volume_set_size_little_endian;
92 	uint16 volume_set_size_big_endian;
93 	uint16 volume_sequence_number_little_endian;
94 	uint16 volume_sequence_number_big_endian;
95 	uint16 logical_block_size_little_endian;
96 	uint16 logical_block_size_big_endian;
97 	uint32 path_table_size_little_endian;
98 	uint32 path_table_size_big_endian;
99 	uint32 ignored02[4];
100 	uchar root_directory_record[34];
101 	char volume_set_identifier[28];
102 	// Remaining bytes are disinteresting to us
103 } __attribute__((packed)) iso9660_primary_volume_descriptor;
104 
105 typedef struct iso9660_supplementary_volume_descriptor {
106 	iso9660_common_volume_descriptor info;
107 	uchar volume_flags;
108 	char system_identifier[32];
109 	char volume_identifier[ISO9660_VOLUME_IDENTIFIER_LENGTH];
110 	uchar unused01[8];
111 	uint32 volume_space_size_little_endian;
112 	uint32 volume_space_size_big_endian;
113 	char escape_sequences[ISO9660_ESCAPE_SEQUENCE_LENGTH];
114 	uint16 volume_set_size_little_endian;
115 	uint16 volume_set_size_big_endian;
116 	uint16 volume_sequence_number_little_endian;
117 	uint16 volume_sequence_number_big_endian;
118 	uint16 logical_block_size_little_endian;
119 	uint16 logical_block_size_big_endian;
120 	uint32 path_table_size_little_endian;
121 	uint32 path_table_size_big_endian;
122 	uint32 ignored02[4];
123 	uchar root_directory_record[34];
124 	char volume_set_identifier[28];
125 	// Remaining bytes are disinteresting to us
126 } __attribute__((packed)) iso9660_supplementary_volume_descriptor;
127 
128 typedef struct iso9660_directory_record {
129 	uint8 length;
130 	uint8 extended_attribute_record_length;
131 	uint32 location_le;
132 	uint32 location_be;
133 	uint32 data_length;
134 	uchar ignored[14];
135 	uint16 volume_space_le;
136 } __attribute__((packed)) iso9660_directory_record;
137 
138 
139 static void dump_directory_record(iso9660_directory_record *record, const char *indent);
140 
141 
142 //----------------------------------------------------------------------------
143 // iso9660_info
144 //----------------------------------------------------------------------------
145 
146 /*! \brief Creates a new iso9660_info struct with empty volume names.
147 
148 	\note Use the applicable set_XYZ_volume_name() functions rather than
149 	messing with the volume name data members directly.
150 */
151 iso9660_info::iso9660_info()
152 	: iso9660_volume_name(NULL)
153 	, joliet_volume_name(NULL)
154 {
155 }
156 
157 /*! \brief Destroys the struct, freeing the volume name strings.
158 */
159 iso9660_info::~iso9660_info()
160 {
161 	if (iso9660_volume_name) {
162 		free(iso9660_volume_name);
163 		iso9660_volume_name = NULL;
164 	}
165 	if (joliet_volume_name) {
166 		free(joliet_volume_name);
167 		joliet_volume_name = NULL;
168 	}
169 }
170 
171 /*! \brief Returns true if a valid volume name exists.
172 */
173 bool
174 iso9660_info::is_valid()
175 {
176 	return iso9660_volume_name || joliet_volume_name;
177 }
178 
179 /*! \brief Sets the iso9660 volume name.
180 
181 	\param name UTF-8 string containing the name.
182 	\param length The length (in bytes) of the string.
183 */
184 void
185 iso9660_info::set_iso9660_volume_name(const char *name, uint32 length)
186 {
187 	set_string(&iso9660_volume_name, name, length);
188 }
189 
190 /*! \brief Sets the Joliet volume name.
191 
192 	\param name UTF-8 string containing the name.
193 	\param length The length (in bytes) of the string.
194 */
195 void
196 iso9660_info::set_joliet_volume_name(const char *name, uint32 length)
197 {
198 	set_string(&joliet_volume_name, name, length);
199 }
200 
201 /*! \brief Returns the volume name of highest precedence.
202 
203 	Currently, the ordering is (decreasingly):
204 	- Joliet
205 	- iso9660
206 */
207 const char*
208 iso9660_info::get_preferred_volume_name()
209 {
210 	if (joliet_volume_name)
211 		return joliet_volume_name;
212 	else
213 		return iso9660_volume_name;
214 }
215 
216 /*! \brief Copies the given string into the old string, managing memory
217 	deallocation and allocation as necessary.
218 */
219 void
220 iso9660_info::set_string(char **string, const char *new_string, uint32 new_length)
221 {
222 	TRACE(("iso9660_info::set_string(%p (`%s'), `%s', %ld)\n", string, *string, new_string, new_length));
223 	if (string) {
224 		char *&old_string = *string;
225 		if (old_string)
226 			free(old_string);
227 		if (new_string) {
228 			old_string = (char*)malloc(new_length+1);
229 			if (old_string) {
230 				strncpy(old_string, new_string, new_length);
231 				old_string[new_length] = 0;
232 			}
233 		} else {
234 			old_string = NULL;
235 		}
236 	}
237 }
238 
239 
240 //	#pragma mark - C functions
241 
242 
243 /*! \brief Converts the given unicode character to utf8.
244 
245 	Courtesy Mr. Axel Dörfler.
246 
247 	\todo Once OpenTracker's locale kit is done, perhaps that functionality
248 	should be used rather than outright stealing the code.
249 */
250 static void
251 unicode_to_utf8(uint32 c, char **out)
252 {
253 	char *s = *out;
254 
255 	if (c < 0x80)
256 		*(s++) = c;
257 	else if (c < 0x800) {
258 		*(s++) = 0xc0 | (c>>6);
259 		*(s++) = 0x80 | (c & 0x3f);
260 	} else if (c < 0x10000) {
261 		*(s++) = 0xe0 | (c>>12);
262 		*(s++) = 0x80 | ((c>>6) & 0x3f);
263 		*(s++) = 0x80 | (c & 0x3f);
264 	} else if (c <= 0x10ffff) {
265 		*(s++) = 0xf0 | (c>>18);
266 		*(s++) = 0x80 | ((c>>12) & 0x3f);
267 		*(s++) = 0x80 | ((c>>6) & 0x3f);
268 		*(s++) = 0x80 | (c & 0x3f);
269 	}
270 	*out = s;
271 }
272 
273 
274 static const char*
275 volume_descriptor_type_to_string(iso9660_volume_descriptor_type type)
276 {
277 	switch (type) {
278 		case ISO9660VD_BOOT:          return "boot";
279 		case ISO9660VD_PRIMARY:       return "primary";
280 		case ISO9660VD_SUPPLEMENTARY: return "supplementary";
281 		case ISO9660VD_PARTITION:     return "partiton";
282 		case ISO9660VD_TERMINATOR:    return "terminator";
283 		default:                      return "invalid";
284 	}
285 }
286 
287 
288 static void
289 dump_common_volume_descriptor(iso9660_common_volume_descriptor *common,
290 	const char *indent, bool print_header)
291 {
292 	if (print_header)
293 		TRACE(("%siso9660_common_volume_descriptor:\n", indent));
294 
295 	TRACE(("%s  volume descriptor type    == %d (%s)\n", indent,
296 	       common->volume_descriptor_type,
297 	       volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type)));
298 	TRACE(("%s  standard identifier       == %.5s (%s)\n", indent, common->standard_identifier,
299 	       strncmp(common->standard_identifier, kISO9660Signature, 5) == 0 ? "valid" : "INVALID"));
300 	TRACE(("%s  volume descriptor version == %d\n", indent, common->volume_descriptor_version));
301 }
302 
303 
304 static void
305 dump_primary_volume_descriptor(iso9660_primary_volume_descriptor *primary,
306 	const char *indent, bool print_header)
307 {
308 	if (print_header)
309 		TRACE(("%siso9660_primary_volume_descriptor:\n", indent));
310 
311 	dump_common_volume_descriptor(&(primary->info), indent, false);
312 	TRACE(("%s  volume identifier         == `%.32s'\n", indent,
313 	       primary->volume_identifier));
314 	TRACE(("%s  volume space size         == %ld\n", indent,
315 	       primary->volume_space_size_little_endian));
316 	TRACE(("%s  volume set size           == %d\n", indent,
317 	       primary->volume_set_size_little_endian));
318 	TRACE(("%s  volume sequence number    == %d\n", indent,
319 	       primary->volume_sequence_number_little_endian));
320 	TRACE(("%s  logical block size        == %d\n", indent,
321 	       primary->logical_block_size_little_endian));
322 	TRACE(("%s  path table size           == %ld\n", indent,
323 	       primary->path_table_size_little_endian));
324 	TRACE(("%s  volume set identifier     == %.28s\n", indent,
325 	       primary->volume_set_identifier));
326 	dump_directory_record((iso9660_directory_record*)primary->root_directory_record, indent);
327 }
328 
329 
330 static void
331 dump_supplementary_volume_descriptor(iso9660_supplementary_volume_descriptor *supplementary,
332 	const char *indent, bool print_header)
333 {
334 	if (print_header)
335 		TRACE(("%siso9660_supplementary_volume_descriptor:\n", indent));
336 
337 	dump_primary_volume_descriptor((iso9660_primary_volume_descriptor*)supplementary,
338 		indent, false);
339 	TRACE(("%s  escape sequences          ==", indent));
340 	for (int i = 0; i < ISO9660_ESCAPE_SEQUENCE_LENGTH; i++) {
341 		TRACE((" %2x", supplementary->escape_sequences[i]));
342 		if (i == ISO9660_ESCAPE_SEQUENCE_LENGTH/2-1)
343 			TRACE(("\n                                "));
344 	}
345 	TRACE(("\n"));
346 }
347 
348 
349 static void
350 dump_directory_record(iso9660_directory_record *record, const char *indent)
351 {
352 	TRACE(("%s  root directory record:\n", indent));
353 	TRACE(("%s    length                    == %d\n", indent, record->length));
354 	TRACE(("%s    location                  == %ld\n", indent, record->location_le));
355 	TRACE(("%s    data length               == %ld\n", indent, record->data_length));
356 	TRACE(("%s    volume sequence number    == %d\n", indent, record->volume_space_le));
357 }
358 
359 
360 static status_t
361 check_common_volume_descriptor(iso9660_common_volume_descriptor *common)
362 {
363 	status_t error = common ? B_OK : B_BAD_VALUE;
364 	if (!error) {
365 		error = strncmp(common->standard_identifier, kISO9660Signature, 5) == 0
366 			? B_OK : B_BAD_DATA;
367 	}
368 	return error;
369 }
370 
371 
372 //	#pragma mark - Public functions
373 
374 
375 // iso9660_fs_identify
376 /*! \brief Returns true if the given partition is a valid iso9660 partition.
377 
378 	See fs_identify_hook() for more information.
379 
380 	\todo Fill in partitionInfo->mounted_at with something useful.
381 */
382 status_t
383 iso9660_fs_identify(int deviceFD, iso9660_info *info)
384 {
385 	char buffer[ISO_PVD_SIZE];
386 	bool exit = false;
387 	status_t error = B_OK;
388 	nspace *vol;
389 
390 	TRACE(("identify(%d, %p)\n", deviceFD, info));
391 	off_t offset = 0x8000;
392 
393 	vol = (nspace *)calloc(sizeof(nspace), 1);
394 	if (vol == NULL)
395 		return ENOMEM;
396 
397 	vol->fd = deviceFD;
398 
399 	// Read through the volume descriptors looking for a primary descriptor.
400 	// If for some reason there are more than one primary descriptor, the
401 	// volume name from the last encountered descriptor will be used.
402 	while (!error && !exit) {// && count++ < 10) {
403 		iso9660_common_volume_descriptor *common = NULL;
404 
405 		// Read the block containing the current descriptor
406 		error = read_pos (vol->fd, offset, (void *)&buffer, ISO_PVD_SIZE);
407 		offset += ISO_PVD_SIZE;
408 		if (error < ISO_PVD_SIZE)
409 			break;
410 
411 		common = (iso9660_common_volume_descriptor*)buffer;
412 		error = check_common_volume_descriptor(common);
413 //		dump_common_volume_descriptor(common, "", true);
414 
415 		// Handle each type of descriptor appropriately
416 		if (!error) {
417 			TRACE(("found %s descriptor\n", volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type)));
418 
419 			switch (common->volume_descriptor_type) {
420 				case ISO9660VD_BOOT:
421 					break;
422 
423 				case ISO9660VD_PRIMARY:
424 				{
425 					int i;
426 					iso9660_primary_volume_descriptor *primary = (iso9660_primary_volume_descriptor*)buffer;
427 
428 					dump_primary_volume_descriptor(primary, "  ", true);
429 
430 					// Cut off any trailing spaces from the volume id. Note
431 					// that this allows for spaces INSIDE the volume id, even
432 					// though that's not technically allowed by the standard;
433 					// this was necessary to support certain RedHat 6.2 CD-ROMs
434 					// from a certain Linux company who shall remain unnamed. ;-)
435 					for (i = ISO9660_VOLUME_IDENTIFIER_LENGTH-1; i >= 0; i--) {
436 						if (primary->volume_identifier[i] != 0x20)
437 							break;
438 					}
439 
440 					// Give a holler if the iso9660 name is already set
441 					if (info->iso9660_volume_name) {
442 						char str[ISO9660_VOLUME_IDENTIFIER_LENGTH+1];
443 						strncpy(str, primary->volume_identifier, i+1);
444 						str[i+1] = 0;
445 						TRACE(("duplicate iso9660 volume name found, using latter (`%s') "
446 						       "instead of former (`%s')\n", str,
447 						       info->iso9660_volume_name));
448 					}
449 
450 					info->set_iso9660_volume_name(primary->volume_identifier, i+1);
451 					info->maxBlocks = primary->volume_set_size_little_endian;
452 					break;
453 				}
454 
455 				case ISO9660VD_SUPPLEMENTARY:
456 				{
457 					iso9660_supplementary_volume_descriptor *supplementary = (iso9660_supplementary_volume_descriptor*)buffer;
458 					dump_supplementary_volume_descriptor((iso9660_supplementary_volume_descriptor*)supplementary, "  ", true);
459 
460 					// Copy and null terminate the escape sequences
461 					char escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH+1];
462 					strncpy(escapes, supplementary->escape_sequences, ISO9660_ESCAPE_SEQUENCE_LENGTH);
463 					escapes[ISO9660_ESCAPE_SEQUENCE_LENGTH] = 0;
464 
465 					// Check for a Joliet VD
466 					if (strstr(escapes, "%/@") || strstr(escapes, "%/C") || strstr(escapes, "%/E")) {
467 						char str[(ISO9660_VOLUME_IDENTIFIER_LENGTH*3/2)+1];
468 							// Since we're dealing with 16-bit Unicode, each UTF-8 sequence
469 							// will be at most 3 bytes long. So we need 3/2 as many chars as
470 							// we start out with.
471 						char *str_iterator = str;
472 						uint16 ch;
473 
474 						// Walk thru the unicode volume name, converting to utf8 as we go.
475 						for (int i = 0;
476 						       (ch = B_BENDIAN_TO_HOST_INT16(((uint16*)supplementary->volume_identifier)[i]))
477 						       && i < ISO9660_VOLUME_IDENTIFIER_LENGTH;
478 						         i++) {
479 							// Give a warning if the character is technically illegal
480 							if (   ch <= 0x001F
481 							    || ch == 0x002A
482 							    || ch == 0x002F
483 							    || ch == 0x003A
484 							    || ch == 0x003B
485 							    || ch == 0x003F
486 							    || ch == 0x005C)
487 							{
488 								TRACE(("warning: illegal Joliet character found: 0%4x\n", ch));
489 							}
490 
491 							// Convert to utf-8
492 							unicode_to_utf8(ch, &str_iterator);
493 						}
494 						*str_iterator = 0;
495 
496 						// Give a holler if the joliet name is already set
497 						if (info->joliet_volume_name) {
498 							TRACE(("duplicate joliet volume name found, using latter (`%s') "
499 							       "instead of former (`%s')\n", str,
500 							       info->joliet_volume_name));
501 						}
502 
503 						info->set_joliet_volume_name(str, strlen(str));
504 					} // end "if Joliet VD"
505 					break;
506 				}
507 
508 				case ISO9660VD_PARTITION:
509 					break;
510 
511 				case ISO9660VD_TERMINATOR:
512 					exit = true;
513 					break;
514 
515 				default:
516 					break;
517 			}
518 		}
519 	}
520 
521 	free(vol);
522 
523 	return error;
524 /*
525 	switch (error) {
526 		case B_OK:
527 			if (info.is_valid()) {
528 				result = true;
529 				if (partitionInfo->file_system_short_name)
530 					strcpy(partitionInfo->file_system_short_name, "iso9660");
531 				if (partitionInfo->file_system_long_name)
532 					strcpy(partitionInfo->file_system_long_name, "iso9660 CD-ROM File System");
533 				partitionInfo->file_system_flags = B_FS_IS_PERSISTENT;
534 				if (priority)
535 					*priority = 0;
536 				// Copy the volume name of highest precedence
537 				if (partitionInfo->volume_name) {
538 					TRACE(("%s: iso9660 name: `%s'\n", kModuleDebugName, info.iso9660_volume_name));
539 					TRACE(("%s: joliet name:  `%s'\n", kModuleDebugName, info.joliet_volume_name));
540 					const char *name = info.get_preferred_volume_name();
541 					int length = strlen(name);
542 					if (length > B_FILE_NAME_LENGTH-1)
543 						length = B_FILE_NAME_LENGTH-1;
544 					strncpy(partitionInfo->volume_name, name, length);
545 					partitionInfo->volume_name[length] = 0;
546 				}
547 				return 0.8;
548 			}
549 			break;
550 
551 		case B_BAD_DATA:
552 			TRACE(("%s: identify: bad signature\n", kModuleDebugName));
553 			break;
554 
555 		default:
556 			TRACE(("%s: identify error: 0x%lx\n", kModuleDebugName,
557 		           error));
558 		    break;
559 	}
560 
561 	return 0.0;
562 */
563 }
564 
565