xref: /haiku/src/add-ons/kernel/file_systems/iso9660/kernel_interface.cpp (revision d2e1e872611179c9cfaa43ce11bd58b1e3554e4b)
1 /*
2  * Copyright 1999, Be Incorporated.   All Rights Reserved.
3  * This file may be used under the terms of the Be Sample Code License.
4  *
5  * Copyright 2001, pinc Software.  All Rights Reserved.
6  *
7  * iso9960/multi-session, 1.0.0
8  */
9 
10 
11 #include <ctype.h>
12 #include <dirent.h>
13 #include <errno.h>
14 #include <fcntl.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <time.h>
20 #include <unistd.h>
21 
22 #include <KernelExport.h>
23 #include <NodeMonitor.h>
24 #include <fs_interface.h>
25 #include <fs_cache.h>
26 
27 #include <fs_attr.h>
28 #include <fs_info.h>
29 #include <fs_index.h>
30 #include <fs_query.h>
31 #include <fs_volume.h>
32 
33 #include <util/kernel_cpp.h>
34 
35 #include "iso9660.h"
36 #include "iso9660_identify.h"
37 
38 
39 //#define TRACE_ISO9660
40 #ifdef TRACE_ISO9660
41 #	define TRACE(x) dprintf x
42 #else
43 #	define TRACE(x) ;
44 #endif
45 
46 
47 struct identify_cookie {
48 	iso9660_info info;
49 };
50 
51 extern fs_volume_ops gISO9660VolumeOps;
52 extern fs_vnode_ops gISO9660VnodeOps;
53 
54 
55 //	#pragma mark - Scanning
56 
57 
58 static float
59 fs_identify_partition(int fd, partition_data *partition, void **_cookie)
60 {
61 	iso9660_info *info = new iso9660_info;
62 
63 	status_t status = iso9660_fs_identify(fd, info);
64 	if (status != B_OK) {
65 		delete info;
66 		return -1;
67 	}
68 
69 	*_cookie = info;
70 	return 0.6f;
71 }
72 
73 
74 static status_t
75 fs_scan_partition(int fd, partition_data *partition, void *_cookie)
76 {
77 	iso9660_info *info = (iso9660_info *)_cookie;
78 
79 	partition->status = B_PARTITION_VALID;
80 	partition->flags |= B_PARTITION_FILE_SYSTEM | B_PARTITION_READ_ONLY ;
81 	partition->block_size = ISO_PVD_SIZE;
82 	partition->content_size = ISO_PVD_SIZE * info->max_blocks;
83 	partition->content_name = strdup(info->PreferredName());
84 
85 	if (partition->content_name == NULL)
86 		return B_NO_MEMORY;
87 
88 	return B_OK;
89 }
90 
91 
92 static void
93 fs_free_identify_partition_cookie(partition_data *partition, void *_cookie)
94 {
95 	delete (iso9660_info *)_cookie;
96 }
97 
98 
99 //	#pragma mark - FS hooks
100 
101 
102 static status_t
103 fs_mount(fs_volume *_volume, const char *device, uint32 flags,
104 	const char *args, ino_t *_rootID)
105 {
106 	bool allowJoliet = true;
107 	nspace *volume;
108 
109 	// Check for a 'nojoliet' parm
110 	// all we check for is the existance of 'nojoliet' in the parms.
111 	if (args != NULL) {
112 		uint32 i;
113 		char *spot;
114 		char *buf = strdup(args);
115 
116 		uint32 len = strlen(buf);
117 		// lower case the parms data
118 		for (i = 0; i < len + 1; i++)
119 			buf[i] = tolower(buf[i]);
120 
121 		// look for nojoliet
122 		spot = strstr(buf, "nojoliet");
123 		if (spot != NULL)
124 			allowJoliet = false;
125 
126 		free(buf);
127 	}
128 
129 	// Try and mount volume as an ISO volume.
130 	status_t result = ISOMount(device, O_RDONLY, &volume, allowJoliet);
131 	if (result == B_OK) {
132 		*_rootID = ISO_ROOTNODE_ID;
133 
134 		_volume->private_volume = volume;
135 		_volume->ops = &gISO9660VolumeOps;
136 		volume->volume = _volume;
137 		volume->id = _volume->id;
138 
139 		result = publish_vnode(_volume, *_rootID, &volume->rootDirRec,
140 			&gISO9660VnodeOps, volume->rootDirRec.attr.stat[FS_DATA_FORMAT].st_mode, 0);
141 		if (result != B_OK) {
142 			block_cache_delete(volume->fBlockCache, false);
143 			free(volume);
144 			result = B_ERROR;
145 		}
146 	}
147 	return result;
148 }
149 
150 
151 static status_t
152 fs_unmount(fs_volume *_vol)
153 {
154 	status_t result = B_NO_ERROR;
155 	nspace *ns = (nspace *)_vol->private_volume;
156 
157 	TRACE(("fs_unmount - ENTER\n"));
158 
159 	// Unlike in BeOS, we need to put the reference to our root node ourselves
160 	put_vnode(_vol, ISO_ROOTNODE_ID);
161 
162 	block_cache_delete(ns->fBlockCache, false);
163 	close(ns->fdOfSession);
164 	result = close(ns->fd);
165 
166 	free(ns);
167 
168 	TRACE(("fs_unmount - EXIT, result is %s\n", strerror(result)));
169 	return result;
170 }
171 
172 
173 static status_t
174 fs_read_fs_stat(fs_volume *_vol, struct fs_info *fss)
175 {
176 	nspace *ns = (nspace *)_vol->private_volume;
177 	int i;
178 
179 	fss->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY;
180 	fss->block_size = ns->logicalBlkSize[FS_DATA_FORMAT];
181 	fss->io_size = 65536;
182 	fss->total_blocks = ns->volSpaceSize[FS_DATA_FORMAT];
183 	fss->free_blocks = 0;
184 
185 	strncpy(fss->device_name, ns->devicePath, sizeof(fss->device_name));
186 
187 	strncpy(fss->volume_name, ns->volIDString, sizeof(fss->volume_name));
188 	for (i = strlen(fss->volume_name) - 1; i >=0 ; i--) {
189 		if (fss->volume_name[i] != ' ')
190 			break;
191 	}
192 
193 	if (i < 0)
194 		strcpy(fss->volume_name, "UNKNOWN");
195 	else
196 		fss->volume_name[i + 1] = 0;
197 
198 	strcpy(fss->fsh_name, "iso9660");
199 	return B_OK;
200 }
201 
202 
203 static status_t
204 fs_get_vnode_name(fs_volume *_vol, fs_vnode *_node, char *buffer, size_t bufferSize)
205 {
206 	vnode *node = (vnode*)_node->private_node;
207 
208 	strlcpy(buffer, node->fileIDString, bufferSize);
209 	return B_OK;
210 }
211 
212 
213 static status_t
214 fs_walk(fs_volume *_vol, fs_vnode *_base, const char *file, ino_t *_vnodeID)
215 {
216 	nspace *ns = (nspace *)_vol->private_volume;
217 	vnode *baseNode = (vnode*)_base->private_node;
218 	vnode *newNode = NULL;
219 
220 	TRACE(("fs_walk - looking for %s in dir file of length %d\n", file,
221 		baseNode->dataLen[FS_DATA_FORMAT]));
222 
223 	if (strcmp(file, ".") == 0)  {
224 		// base directory
225 		TRACE(("fs_walk - found \".\" file.\n"));
226 		*_vnodeID = baseNode->id;
227 		return get_vnode(_vol, *_vnodeID, (void **)&newNode);
228 	} else if (strcmp(file, "..") == 0) {
229 		// parent directory
230 		TRACE(("fs_walk - found \"..\" file.\n"));
231 		*_vnodeID = baseNode->parID;
232 		return get_vnode(_vol, *_vnodeID, (void **)&newNode);
233 	}
234 
235 	// look up file in the directory
236 	uint32 dataLength = baseNode->dataLen[FS_DATA_FORMAT];
237 	status_t result = ENOENT;
238 	uint32 totalRead = 0;
239 	off_t block = baseNode->startLBN[FS_DATA_FORMAT];
240 	bool done = false;
241 
242 	while (totalRead < dataLength && !done) {
243 		off_t cachedBlock = block;
244 		char *blockData = (char *)block_cache_get_etc(ns->fBlockCache, block, 0,
245 			ns->logicalBlkSize[FS_DATA_FORMAT]);
246 		if (blockData != NULL) {
247 			int bytesRead = 0;
248 			off_t blockBytesRead = 0;
249 			vnode node;
250 			int initResult;
251 
252 			TRACE(("fs_walk - read buffer from disk at LBN %Ld into buffer 0x%x.\n",
253 				block, blockData));
254 
255 			// Move to the next 2-block set if necessary
256 			// Don't go over end of buffer, if dir record sits on boundary.
257 
258 			node.fileIDString = NULL;
259 			node.attr.slName = NULL;
260 
261 			while (blockBytesRead < 2 * ns->logicalBlkSize[FS_DATA_FORMAT]
262 				&& totalRead + blockBytesRead < dataLength
263 				&& blockData[0] != 0
264 				&& !done) {
265 				initResult = InitNode(&node, blockData, &bytesRead,
266 					ns->joliet_level);
267 				TRACE(("fs_walk - InitNode returned %s, filename %s, %d bytes read\n", strerror(initResult), node.fileIDString, bytesRead));
268 
269 				if (initResult == B_OK) {
270 					if (!strcmp(node.fileIDString, file)) {
271 						TRACE(("fs_walk - success, found vnode at block %Ld, pos %Ld\n", block, blockBytesRead));
272 						*_vnodeID = (block << 30)
273 							+ (blockBytesRead & 0xffffffff);
274 						TRACE(("fs_walk - New vnode id is %Ld\n", *_vnodeID));
275 
276 						result = get_vnode(_vol, *_vnodeID,
277 							(void **)&newNode);
278 						if (result == B_OK) {
279 							newNode->parID = baseNode->id;
280 							done = true;
281 						}
282 					} else {
283 						free(node.fileIDString);
284 						node.fileIDString = NULL;
285 						free(node.attr.slName);
286 						node.attr.slName = NULL;
287 					}
288 				} else {
289 					result = initResult;
290 					if (bytesRead == 0)
291 						done = TRUE;
292 				}
293 				blockData += bytesRead;
294 				blockBytesRead += bytesRead;
295 
296 				TRACE(("fs_walk - Adding %d bytes to blockBytes read (total %Ld/%Ld).\n",
297 					bytesRead, blockBytesRead, baseNode->dataLen[FS_DATA_FORMAT]));
298 			}
299 			totalRead += ns->logicalBlkSize[FS_DATA_FORMAT];
300 			block++;
301 
302 			TRACE(("fs_walk - moving to next block %Ld, total read %Ld\n", block, totalRead));
303 			block_cache_put(ns->fBlockCache, cachedBlock);
304 
305 		} else
306 			done = TRUE;
307 	}
308 
309 	TRACE(("fs_walk - EXIT, result is %s, vnid is %Lu\n",
310 		strerror(result), *_vnodeID));
311 	return result;
312 }
313 
314 
315 static status_t
316 fs_read_vnode(fs_volume *_vol, ino_t vnodeID, fs_vnode *_node,
317 	int *_type, uint32 *_flags, bool reenter)
318 {
319 	nspace *ns = (nspace*)_vol->private_volume;
320 
321 	vnode *newNode = (vnode*)calloc(sizeof(vnode), 1);
322 	if (newNode == NULL)
323 		return B_NO_MEMORY;
324 
325 	uint32 pos = vnodeID & 0x3fffffff;
326 	uint32 block = vnodeID >> 30;
327 
328 	TRACE(("fs_read_vnode - block = %ld, pos = %ld, raw = %Lu node 0x%x\n",
329 		block, pos, vnodeID, newNode));
330 
331 	if (pos > ns->logicalBlkSize[FS_DATA_FORMAT])
332 		return B_BAD_VALUE;
333 
334 	char *data = (char *)block_cache_get_etc(ns->fBlockCache,
335 		block, 0, ns->logicalBlkSize[FS_DATA_FORMAT]);
336 	if (data == NULL) {
337 		free(newNode);
338 		return B_IO_ERROR;
339 	}
340 
341 	status_t result = InitNode(newNode, data + pos, NULL, ns->joliet_level);
342 	block_cache_put(ns->fBlockCache, block);
343 
344 	if (result < B_OK) {
345 		free(newNode);
346 		return result;
347 	}
348 
349 	newNode->id = vnodeID;
350 	_node->private_node = newNode;
351 	_node->ops = &gISO9660VnodeOps;
352 	*_type = newNode->attr.stat[FS_DATA_FORMAT].st_mode & ~(S_IWUSR | S_IWGRP | S_IWOTH);
353 	*_flags = 0;
354 
355 	if ((newNode->flags & ISO_ISDIR) == 0) {
356 		newNode->cache = file_cache_create(ns->id, vnodeID,
357 			newNode->dataLen[FS_DATA_FORMAT]);
358 	}
359 
360 	return B_OK;
361 }
362 
363 
364 static status_t
365 fs_release_vnode(fs_volume *_vol, fs_vnode *_node, bool reenter)
366 {
367 	status_t result = B_NO_ERROR;
368 	vnode *node = (vnode*)_node->private_node;
369 
370 	(void)_vol;
371 	(void)reenter;
372 
373 	TRACE(("fs_release_vnode - ENTER (0x%x)\n", node));
374 
375 	if (node != NULL) {
376 		if (node->id != ISO_ROOTNODE_ID) {
377 			if (node->fileIDString != NULL)
378 				free (node->fileIDString);
379 			if (node->attr.slName != NULL)
380 				free (node->attr.slName);
381 			if (node->cache != NULL)
382 				file_cache_delete(node->cache);
383 
384 			free(node);
385 		}
386 	}
387 
388 	TRACE(("fs_release_vnode - EXIT\n"));
389 	return result;
390 }
391 
392 
393 static status_t
394 fs_read_pages(fs_volume *_vol, fs_vnode *_node, void * _cookie, off_t pos,
395 	const iovec *vecs, size_t count, size_t *_numBytes, bool reenter)
396 {
397 	nspace *ns = (nspace *)_vol->private_volume;
398 	vnode *node = (vnode *)_node->private_node;
399 
400 	uint32 fileSize = node->dataLen[FS_DATA_FORMAT];
401 	size_t bytesLeft = *_numBytes;
402 
403 	if (pos >= fileSize) {
404 		*_numBytes = 0;
405 		return B_OK;
406 	}
407 	if (pos + bytesLeft > fileSize) {
408 		bytesLeft = fileSize - pos;
409 		*_numBytes = bytesLeft;
410 	}
411 
412 	file_io_vec fileVec;
413 	fileVec.offset = pos + node->startLBN[FS_DATA_FORMAT]
414 		* ns->logicalBlkSize[FS_DATA_FORMAT];
415 	fileVec.length = bytesLeft;
416 
417 	uint32 vecIndex = 0;
418 	size_t vecOffset = 0;
419 	return read_file_io_vec_pages(ns->fd, &fileVec, 1, vecs, count,
420 		&vecIndex, &vecOffset, &bytesLeft);
421 }
422 
423 
424 static status_t
425 fs_read_stat(fs_volume *_vol, fs_vnode *_node, struct stat *st)
426 {
427 	nspace *ns = (nspace*)_vol->private_volume;
428 	vnode *node = (vnode*)_node->private_node;
429 	status_t result = B_NO_ERROR;
430 	time_t time;
431 
432 	TRACE(("fs_read_stat - ENTER\n"));
433 
434 	st->st_dev = ns->id;
435 	st->st_ino = node->id;
436 	st->st_nlink = node->attr.stat[FS_DATA_FORMAT].st_nlink;
437 	st->st_uid = node->attr.stat[FS_DATA_FORMAT].st_uid;
438 	st->st_gid = node->attr.stat[FS_DATA_FORMAT].st_gid;
439 	st->st_blksize = 65536;
440 	st->st_mode = node->attr.stat[FS_DATA_FORMAT].st_mode;
441 
442 	// Same for file/dir in ISO9660
443 	st->st_size = node->dataLen[FS_DATA_FORMAT];
444 	if (ConvertRecDate(&(node->recordDate), &time) == B_NO_ERROR)
445 		st->st_ctime = st->st_mtime = st->st_atime = time;
446 
447 	TRACE(("fs_read_stat - EXIT, result is %s\n", strerror(result)));
448 
449 	return result;
450 }
451 
452 
453 static status_t
454 fs_open(fs_volume *_vol, fs_vnode *_node, int omode, void **cookie)
455 {
456 	status_t result = B_NO_ERROR;
457 
458 	(void)_vol;
459 	(void)cookie;
460 
461 	// Do not allow any of the write-like open modes to get by
462 	if ((omode == O_WRONLY) || (omode == O_RDWR))
463 		result = EROFS;
464 	else if((omode & O_TRUNC) || (omode & O_CREAT))
465 		result = EROFS;
466 
467 	return result;
468 }
469 
470 
471 static status_t
472 fs_read(fs_volume *_vol, fs_vnode *_node, void *cookie, off_t pos, void *buffer,
473 	size_t *_length)
474 {
475 	vnode *node = (vnode *)_node->private_node;
476 
477 	if (node->flags & ISO_ISDIR)
478 		return EISDIR;
479 
480 	uint32 fileSize = node->dataLen[FS_DATA_FORMAT];
481 
482 	// set/check boundaries for pos/length
483 	if (pos < 0)
484 		return B_BAD_VALUE;
485 	if (pos >= fileSize) {
486 		*_length = 0;
487 		return B_OK;
488 	}
489 
490 	return file_cache_read(node->cache, NULL, pos, buffer, _length);
491 }
492 
493 
494 static status_t
495 fs_close(fs_volume *_vol, fs_vnode *_node, void *cookie)
496 {
497 	(void)_vol;
498 	(void)_node;
499 	(void)cookie;
500 
501 	return B_OK;
502 }
503 
504 
505 static status_t
506 fs_free_cookie(fs_volume *_vol, fs_vnode *_node, void *cookie)
507 {
508 	(void)_vol;
509 	(void)_node;
510 	(void)cookie;
511 
512 	return B_OK;
513 }
514 
515 
516 static status_t
517 fs_access(fs_volume *_vol, fs_vnode *_node, int mode)
518 {
519 	(void)_vol;
520 	(void)_node;
521 	(void)mode;
522 
523 	return B_OK;
524 }
525 
526 
527 static status_t
528 fs_read_link(fs_volume *_vol, fs_vnode *_node, char *buffer, size_t *_bufferSize)
529 {
530 	vnode *node = (vnode *)_node->private_node;
531 
532 	if (!S_ISLNK(node->attr.stat[FS_DATA_FORMAT].st_mode))
533 		return B_BAD_VALUE;
534 
535 	size_t length = strlen(node->attr.slName);
536 	if (length > *_bufferSize)
537 		memcpy(buffer, node->attr.slName, *_bufferSize);
538 	else {
539 		memcpy(buffer, node->attr.slName, length);
540 		*_bufferSize = length;
541 	}
542 
543 	return B_OK;
544 }
545 
546 
547 static status_t
548 fs_open_dir(fs_volume *_vol, fs_vnode *_node, void **cookie)
549 {
550 	vnode *node = (vnode *)_node->private_node;
551 
552 	TRACE(("fs_open_dir - node is 0x%x\n", _node));
553 
554 	if (!(node->flags & ISO_ISDIR))
555 		return B_NOT_A_DIRECTORY;
556 
557 	dircookie *dirCookie = (dircookie *)malloc(sizeof(dircookie));
558 	if (dirCookie == NULL)
559 		return B_NO_MEMORY;
560 
561 	dirCookie->startBlock = node->startLBN[FS_DATA_FORMAT];
562 	dirCookie->block = node->startLBN[FS_DATA_FORMAT];
563 	dirCookie->totalSize = node->dataLen[FS_DATA_FORMAT];
564 	dirCookie->pos = 0;
565 	dirCookie->id = node->id;
566 	*cookie = (void *)dirCookie;
567 
568 	return B_OK;
569 }
570 
571 
572 static status_t
573 fs_read_dir(fs_volume *_vol, fs_vnode *_node, void *_cookie, struct dirent *buffer,
574 	size_t bufferSize, uint32 *num)
575 {
576 	nspace *ns = (nspace *)_vol->private_volume;
577 	dircookie *dirCookie = (dircookie *)_cookie;
578 
579 	TRACE(("fs_read_dir - ENTER\n"));
580 
581 	status_t result = ISOReadDirEnt(ns, dirCookie, buffer, bufferSize);
582 
583 	// If we succeeded, return 1, the number of dirents we read.
584 	if (result == B_OK)
585 		*num = 1;
586 	else
587 		*num = 0;
588 
589 	// When you get to the end, don't return an error, just return
590 	// a zero in *num.
591 
592 	if (result == ENOENT)
593 		result = B_OK;
594 
595 	TRACE(("fs_read_dir - EXIT, result is %s\n", strerror(result)));
596 	return result;
597 }
598 
599 
600 static status_t
601 fs_rewind_dir(fs_volume *_vol, fs_vnode *_node, void* _cookie)
602 {
603 	dircookie *cookie = (dircookie*)_cookie;
604 
605 	cookie->block = cookie->startBlock;
606 	cookie->pos = 0;
607 	return B_OK;
608 }
609 
610 
611 static status_t
612 fs_close_dir(fs_volume *_vol, fs_vnode *_node, void *cookie)
613 {
614 	return B_OK;
615 }
616 
617 
618 static status_t
619 fs_free_dir_cookie(fs_volume *_vol, fs_vnode *_node, void *cookie)
620 {
621 	free(cookie);
622 	return B_OK;
623 }
624 
625 
626 //	#pragma mark -
627 
628 
629 static status_t
630 iso_std_ops(int32 op, ...)
631 {
632 	switch (op) {
633 		case B_MODULE_INIT:
634 		case B_MODULE_UNINIT:
635 			return B_OK;
636 		default:
637 			return B_ERROR;
638 	}
639 }
640 
641 
642 fs_volume_ops gISO9660VolumeOps = {
643 	&fs_unmount,
644 	&fs_read_fs_stat,
645 	NULL,
646 	NULL,
647 	&fs_read_vnode,
648 
649 	/* index and index directory ops */
650 	NULL,
651 	NULL,
652 	NULL,
653 	NULL,
654 	NULL,
655 	NULL,
656 	NULL,
657 	NULL,
658 
659 	/* query ops */
660 	NULL,
661 	NULL,
662 	NULL,
663 	NULL,
664 	NULL,
665 
666 	/* FS layer ops */
667 	NULL,
668 	NULL,
669 };
670 
671 fs_vnode_ops gISO9660VnodeOps = {
672 	&fs_walk,
673 	&fs_get_vnode_name,
674 	&fs_release_vnode,
675 	NULL,
676 
677 	/* vm-related ops */
678 	NULL,
679 	&fs_read_pages,
680 	NULL,
681 
682 	/* cache file access */
683 	NULL,
684 
685 	/* common */
686 	NULL,
687 	NULL,
688 	NULL,
689 	NULL,
690 	NULL,
691 	&fs_read_link,
692 	NULL,
693 	NULL,
694 	NULL,
695 	NULL,
696 	&fs_access,
697 	&fs_read_stat,
698 	NULL,
699 
700 	/* file */
701 	NULL,
702 	&fs_open,
703 	&fs_close,
704 	&fs_free_cookie,
705 	&fs_read,
706 	NULL,
707 
708 	/* dir */
709 	NULL,
710 	NULL,
711 	&fs_open_dir,
712 	&fs_close_dir,
713 	&fs_free_dir_cookie,
714 	&fs_read_dir,
715 	&fs_rewind_dir,
716 
717 	/* attribute directory ops */
718 	NULL,
719 	NULL,
720 	NULL,
721 	NULL,
722 	NULL,
723 
724 	/* attribute ops */
725 	NULL,
726 	NULL,
727 	NULL,
728 	NULL,
729 	NULL,
730 	NULL,
731 	NULL,
732 	NULL,
733 	NULL,
734 	NULL,
735 
736 	/* node and FS layer support */
737 	NULL,
738 	NULL,
739 };
740 
741 static file_system_module_info sISO660FileSystem = {
742 	{
743 		"file_systems/iso9660" B_CURRENT_FS_API_VERSION,
744 		0,
745 		iso_std_ops,
746 	},
747 
748 	"ISO9660 File System",
749 	0,	// DDM flags
750 
751 	// scanning
752 	fs_identify_partition,
753 	fs_scan_partition,
754 	fs_free_identify_partition_cookie,
755 	NULL,	// free_partition_content_cookie()
756 
757 	&fs_mount,
758 
759 	/* capability querying */
760 	NULL,
761 	NULL,
762 	NULL,
763 	NULL,
764 	NULL,
765 	NULL,
766 
767 	/* shadow partition modifications */
768 	NULL,
769 
770 	/* writing */
771 	NULL,
772 	NULL,
773 	NULL,
774 	NULL,
775 	NULL,
776 	NULL,
777 	NULL,
778 };
779 
780 module_info *modules[] = {
781 	(module_info *)&sISO660FileSystem,
782 	NULL,
783 };
784 
785