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