xref: /haiku/src/add-ons/kernel/file_systems/iso9660/iso9660.cpp (revision d3d8b26997fac34a84981e6d2b649521de2cc45a)
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 // iso9660_info
140 //----------------------------------------------------------------------------
141 
142 /*! \brief Creates a new iso9660_info struct with empty volume names.
143 
144 	\note Use the applicable set_XYZ_volume_name() functions rather than
145 	messing with the volume name data members directly.
146 */
147 iso9660_info::iso9660_info()
148 	: iso9660_volume_name(NULL)
149 	, joliet_volume_name(NULL)
150 {
151 }
152 
153 /*! \brief Destroys the struct, freeing the volume name strings.
154 */
155 iso9660_info::~iso9660_info()
156 {
157 	if (iso9660_volume_name) {
158 		free(iso9660_volume_name);
159 		iso9660_volume_name = NULL;
160 	}
161 	if (joliet_volume_name) {
162 		free(joliet_volume_name);
163 		joliet_volume_name = NULL;
164 	}
165 }
166 
167 /*! \brief Returns true if a valid volume name exists.
168 */
169 bool
170 iso9660_info::is_valid()
171 {
172 	return iso9660_volume_name || joliet_volume_name;
173 }
174 
175 /*! \brief Sets the iso9660 volume name.
176 
177 	\param name UTF-8 string containing the name.
178 	\param length The length (in bytes) of the string.
179 */
180 void
181 iso9660_info::set_iso9660_volume_name(const char *name, uint32 length)
182 {
183 	set_string(&iso9660_volume_name, name, length);
184 }
185 
186 /*! \brief Sets the Joliet volume name.
187 
188 	\param name UTF-8 string containing the name.
189 	\param length The length (in bytes) of the string.
190 */
191 void
192 iso9660_info::set_joliet_volume_name(const char *name, uint32 length)
193 {
194 	set_string(&joliet_volume_name, name, length);
195 }
196 
197 /*! \brief Returns the volume name of highest precedence.
198 
199 	Currently, the ordering is (decreasingly):
200 	- Joliet
201 	- iso9660
202 */
203 const char*
204 iso9660_info::get_preferred_volume_name()
205 {
206 	if (joliet_volume_name)
207 		return joliet_volume_name;
208 	else
209 		return iso9660_volume_name;
210 }
211 
212 /*! \brief Copies the given string into the old string, managing memory
213 	deallocation and allocation as necessary.
214 */
215 void
216 iso9660_info::set_string(char **string, const char *new_string, uint32 new_length)
217 {
218 	TRACE(("iso9660_info::set_string(%p (`%s'), `%s', %ld)\n", string, *string, new_string, new_length));
219 	if (string) {
220 		char *&old_string = *string;
221 		if (old_string)
222 			free(old_string);
223 		if (new_string) {
224 			old_string = (char*)malloc(new_length+1);
225 			if (old_string) {
226 				strncpy(old_string, new_string, new_length);
227 				old_string[new_length] = 0;
228 			}
229 		} else {
230 			old_string = NULL;
231 		}
232 	}
233 }
234 
235 //----------------------------------------------------------------------------
236 // C functions
237 //----------------------------------------------------------------------------
238 
239 /*! \brief Converts the given unicode character to utf8.
240 
241 	Courtesy Mr. Axel Dörfler.
242 
243 	\todo Once OpenTracker's locale kit is done, perhaps that functionality
244 	should be used rather than outright stealing the code.
245 */
246 static
247 void
248 unicode_to_utf8(uint32 c, char **out)
249 {
250 	char *s = *out;
251 
252 	if (c < 0x80)
253 		*(s++) = c;
254 	else if (c < 0x800) {
255 		*(s++) = 0xc0 | (c>>6);
256 		*(s++) = 0x80 | (c & 0x3f);
257 	} else if (c < 0x10000) {
258 		*(s++) = 0xe0 | (c>>12);
259 		*(s++) = 0x80 | ((c>>6) & 0x3f);
260 		*(s++) = 0x80 | (c & 0x3f);
261 	} else if (c <= 0x10ffff) {
262 		*(s++) = 0xf0 | (c>>18);
263 		*(s++) = 0x80 | ((c>>12) & 0x3f);
264 		*(s++) = 0x80 | ((c>>6) & 0x3f);
265 		*(s++) = 0x80 | (c & 0x3f);
266 	}
267 	*out = s;
268 }
269 
270 static
271 const char*
272 volume_descriptor_type_to_string(iso9660_volume_descriptor_type type)
273 {
274 	switch (type) {
275 		case ISO9660VD_BOOT:          return "boot";
276 		case ISO9660VD_PRIMARY:       return "primary";
277 		case ISO9660VD_SUPPLEMENTARY: return "supplementary";
278 		case ISO9660VD_PARTITION:     return "partiton";
279 		case ISO9660VD_TERMINATOR:    return "terminator";
280 		default:                      return "invalid";
281 	}
282 }
283 
284 static
285 void
286 dump_common_volume_descriptor(iso9660_common_volume_descriptor *common, const char *indent,
287                               bool print_header)
288 {
289 	if (print_header)
290 		TRACE(("%siso9660_common_volume_descriptor:\n", indent));
291 
292 	TRACE(("%s  volume descriptor type    == %d (%s)\n", indent,
293 	       common->volume_descriptor_type,
294 	       volume_descriptor_type_to_string((iso9660_volume_descriptor_type)common->volume_descriptor_type)));
295 	TRACE(("%s  standard identifier       == %.5s (%s)\n", indent, common->standard_identifier,
296 	       strncmp(common->standard_identifier, kISO9660Signature, 5) == 0 ? "valid" : "INVALID"));
297 	TRACE(("%s  volume descriptor version == %d\n", indent, common->volume_descriptor_version));
298 }
299 
300 static
301 void
302 dump_directory_record(iso9660_directory_record *record, const char *indent);
303 
304 static
305 void
306 dump_primary_volume_descriptor(iso9660_primary_volume_descriptor *primary, const char *indent,
307                               bool print_header)
308 {
309 	if (print_header)
310 		TRACE(("%siso9660_primary_volume_descriptor:\n", indent));
311 
312 	dump_common_volume_descriptor(&(primary->info), indent, false);
313 	TRACE(("%s  volume identifier         == `%.32s'\n", indent,
314 	       primary->volume_identifier));
315 	TRACE(("%s  volume space size         == %ld\n", indent,
316 	       primary->volume_space_size_little_endian));
317 	TRACE(("%s  volume set size           == %d\n", indent,
318 	       primary->volume_set_size_little_endian));
319 	TRACE(("%s  volume sequence number    == %d\n", indent,
320 	       primary->volume_sequence_number_little_endian));
321 	TRACE(("%s  logical block size        == %d\n", indent,
322 	       primary->logical_block_size_little_endian));
323 	TRACE(("%s  path table size           == %ld\n", indent,
324 	       primary->path_table_size_little_endian));
325 	TRACE(("%s  volume set identifier     == %.28s\n", indent,
326 	       primary->volume_set_identifier));
327 	dump_directory_record((iso9660_directory_record*)primary->root_directory_record, indent);
328 }
329 
330 static
331 void
332 dump_supplementary_volume_descriptor(iso9660_supplementary_volume_descriptor *supplementary, const char *indent,
333                               bool print_header)
334 {
335 	if (print_header)
336 		TRACE(("%siso9660_supplementary_volume_descriptor:\n", indent));
337 
338 	dump_primary_volume_descriptor((iso9660_primary_volume_descriptor*)supplementary, 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 static
349 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 static
360 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,
366 		                5) == 0 ? B_OK : B_BAD_DATA;
367 	}
368 	return error;
369 }
370 
371 
372 // iso9660_fs_identify
373 /*! \brief Returns true if the given partition is a valid iso9660 partition.
374 
375 	See fs_identify_hook() for more information.
376 
377 	\todo Fill in partitionInfo->mounted_at with something useful.
378 */
379 status_t
380 iso9660_fs_identify(int deviceFD, iso9660_info *info)
381 {
382 	bool result = false;
383 	uchar *buffer = NULL;
384 	bool exit = false;
385 	status_t error = B_OK;
386 	nspace *vol;
387 
388 	TRACE(("identify(%d, %p)\n", deviceFD, info));
389 	off_t offset = 0x8000;
390 
391 	vol = (nspace *)calloc(sizeof(nspace), 1);
392         if (vol == NULL) {
393                 return ENOMEM;
394         }
395 
396 	vol->fd = deviceFD;
397 
398 	// Read through the volume descriptors looking for a primary descriptor.
399 	// If for some reason there are more than one primary descriptor, the
400 	// volume name from the last encountered descriptor will be used.
401 	while (!error && !exit) {// && count++ < 10) {
402 		iso9660_common_volume_descriptor *common = NULL;
403 
404 		// Read the block containing the current descriptor
405 		error = read_pos (vol->fdOfSession, offset, (void *)&buffer, ISO_PVD_SIZE);
406 		offset += ISO_PVD_SIZE;
407 		if (error < ISO_PVD_SIZE) {
408                         break;
409                 }
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 		if (buffer) {
520 			free(buffer);
521 			buffer = NULL;
522 		}
523 	}
524 
525 	free(vol);
526 
527 	return error;
528 /*
529 	switch (error) {
530 		case B_OK:
531 			if (info.is_valid()) {
532 				result = true;
533 				if (partitionInfo->file_system_short_name)
534 					strcpy(partitionInfo->file_system_short_name, "iso9660");
535 				if (partitionInfo->file_system_long_name)
536 					strcpy(partitionInfo->file_system_long_name, "iso9660 CD-ROM File System");
537 				partitionInfo->file_system_flags = B_FS_IS_PERSISTENT;
538 				if (priority)
539 					*priority = 0;
540 				// Copy the volume name of highest precedence
541 				if (partitionInfo->volume_name) {
542 					TRACE(("%s: iso9660 name: `%s'\n", kModuleDebugName, info.iso9660_volume_name));
543 					TRACE(("%s: joliet name:  `%s'\n", kModuleDebugName, info.joliet_volume_name));
544 					const char *name = info.get_preferred_volume_name();
545 					int length = strlen(name);
546 					if (length > B_FILE_NAME_LENGTH-1)
547 						length = B_FILE_NAME_LENGTH-1;
548 					strncpy(partitionInfo->volume_name, name, length);
549 					partitionInfo->volume_name[length] = 0;
550 				}
551 				return 0.8;
552 			}
553 			break;
554 
555 		case B_BAD_DATA:
556 			TRACE(("%s: identify: bad signature\n", kModuleDebugName));
557 			break;
558 
559 		default:
560 			TRACE(("%s: identify error: 0x%lx\n", kModuleDebugName,
561 		           error));
562 		    break;
563 	}
564 
565 	return 0.0;
566 */
567 }
568 
569