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