xref: /haiku/src/add-ons/kernel/file_systems/udf/kernel_interface.cpp (revision 15fb7d88e971c4d6c787c6a3a5c159afb1ebf77b)
1 /*
2  * Copyright 2012, Jérôme Duval, korli@users.berlios.de.
3  * Copyright 2010, Michael Lotz, mmlr@mlotz.ch.
4  * Copyright 2008, Salvatore Benedetto, salvatore.benedetto@gmail.com.
5  * Copyright 2003, Tyler Dauwalder, tyler@dauwalder.net.
6  * Distributed under the terms of the MIT License.
7  */
8 
9 
10 /*! \file kernel_interface.cpp */
11 
12 
13 #include <Drivers.h>
14 #include <ctype.h>
15 #include <errno.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 
20 #include <KernelExport.h>
21 #include <util/kernel_cpp.h>
22 
23 #include <io_requests.h>
24 
25 #include "Icb.h"
26 #include "Recognition.h"
27 #include "Utils.h"
28 #include "Volume.h"
29 
30 
31 #undef TRACE
32 #undef TRACE_ERROR
33 //#define UDF_KERNEL_INTERFACE_DEBUG
34 #ifdef UDF_KERNEL_INTERFACE_DEBUG
35 #	define TRACE(x)			dprintf x
36 #	define TRACE_ERROR(x)	dprintf x
37 #else
38 #	define TRACE(x)			/* nothing */
39 #	define TRACE_ERROR(x)	dprintf x
40 #endif
41 
42 extern fs_volume_ops gUDFVolumeOps;
43 extern fs_vnode_ops gUDFVnodeOps;
44 
45 
46 struct identify_cookie {
47 	struct logical_volume_descriptor logical_volume_descriptor;
48 };
49 
50 
51 //	#pragma mark - io callbacks
52 
53 
54 static status_t
55 iterative_io_get_vecs_hook(void *cookie, io_request *request, off_t offset,
56 	size_t size, struct file_io_vec *vecs, size_t *count)
57 {
58 	Icb *icb = (Icb *)cookie;
59 	return file_map_translate(icb->FileMap(), offset, size, vecs, count,
60 		icb->GetVolume()->BlockSize());
61 }
62 
63 
64 static status_t
65 iterative_io_finished_hook(void *cookie, io_request *request, status_t status,
66 	bool partialTransfer, size_t bytesTransferred)
67 {
68 	// nothing to do
69 	return B_OK;
70 }
71 
72 
73 //	#pragma mark - fs_volume_ops fuctions
74 
75 
76 static float
77 udf_identify_partition(int fd, partition_data *partition, void **_cookie)
78 {
79 	TRACE(("udf_identify_partition: fd = %d, id = %ld, offset = %Ld, size = %Ld "
80 		"content_size = %Ld, block_size = %lu\n", fd, partition->id,
81 		partition->offset, partition->size, partition->content_size,
82 		partition->block_size));
83 
84 	primary_volume_descriptor primaryVolumeDescriptor;
85 	logical_volume_descriptor logicalVolumeDescriptor;
86 	partition_descriptor partitionDescriptors[kMaxPartitionDescriptors];
87 	uint8 descriptorCount = kMaxPartitionDescriptors;
88 	uint32 blockShift;
89 	status_t error = udf_recognize(fd, partition->offset, partition->size,
90 		partition->block_size, blockShift, primaryVolumeDescriptor,
91 		logicalVolumeDescriptor, partitionDescriptors, descriptorCount);
92 	if (error != B_OK)
93 		return -1;
94 
95 	identify_cookie *cookie = new(std::nothrow) identify_cookie;
96 	if (cookie == NULL)
97 		return -1;
98 
99 	cookie->logical_volume_descriptor = logicalVolumeDescriptor;
100 	*_cookie = cookie;
101 	return 0.8f;
102 }
103 
104 
105 static status_t
106 udf_scan_partition(int fd, partition_data *partition, void *_cookie)
107 {
108 	TRACE(("udf_scan_partition: fd = %d\n", fd));
109 	identify_cookie *cookie = (identify_cookie *)_cookie;
110 	logical_volume_descriptor &volumeDescriptor
111 		= cookie->logical_volume_descriptor;
112 
113 	partition->status = B_PARTITION_VALID;
114 	partition->flags |= B_PARTITION_FILE_SYSTEM;
115 	partition->content_size = partition->size;
116 		// TODO: not actually correct
117 	partition->block_size = volumeDescriptor.logical_block_size();
118 
119 	UdfString name(volumeDescriptor.logical_volume_identifier());
120 	partition->content_name = strdup(name.Utf8());
121 	if (partition->content_name == NULL)
122 		return B_NO_MEMORY;
123 
124 	return B_OK;
125 }
126 
127 
128 static void
129 udf_free_identify_partition_cookie(partition_data *partition, void *cookie)
130 {
131 	delete (identify_cookie *)cookie;
132 }
133 
134 
135 static status_t
136 udf_unmount(fs_volume *_volume)
137 {
138 	TRACE(("udb_unmount: _volume = %p\n", _volume));
139 	Volume *volume = (Volume *)_volume->private_volume;
140 	delete volume;
141 	return B_OK;
142 }
143 
144 
145 static status_t
146 udf_read_fs_stat(fs_volume *_volume, struct fs_info *info)
147 {
148 	TRACE(("udf_read_fs_stat: _volume = %p, info = %p\n", _volume, info));
149 
150 	Volume *volume = (Volume *)_volume->private_volume;
151 
152 	// File system flags.
153 	info->flags = B_FS_IS_PERSISTENT | B_FS_IS_READONLY;
154 
155 	info->io_size = 65536;
156 		// whatever is appropriate here? Just use the same value as BFS (and iso9660) for now
157 
158 	info->block_size = volume->BlockSize();
159 	info->total_blocks = volume->Length();
160 	info->free_blocks = 0;
161 
162 	// Volume name
163 	sprintf(info->volume_name, "%s", volume->Name());
164 
165 	// File system name
166 	strcpy(info->fsh_name, "udf");
167 
168 	return B_OK;
169 }
170 
171 
172 static status_t
173 udf_get_vnode(fs_volume *_volume, ino_t id, fs_vnode *node, int *_type,
174 	uint32 *_flags, bool reenter)
175 {
176 	TRACE(("udf_get_vnode: _volume = %p, _node = %p, reenter = %s\n",
177 		_volume, _node, (reenter ? "true" : "false")));
178 
179 	Volume *volume = (Volume *)_volume->private_volume;
180 
181 	// Convert the given vnode id to an address, and create
182 	// and return a corresponding Icb object for it.
183 	TRACE(("udf_get_vnode: id = %Ld, blockSize = %lu\n", id,
184 		volume->BlockSize()));
185 
186 	Icb *icb = new(std::nothrow) Icb(volume,
187 		to_long_address(id, volume->BlockSize()));
188 	if (icb == NULL)
189 		return B_NO_MEMORY;
190 
191 	if (icb->InitCheck() == B_OK) {
192 		node->private_node = icb;
193 		node->ops = &gUDFVnodeOps;
194 		*_type = icb->Mode();
195 		*_flags = 0;
196 		return B_OK;
197 	}
198 
199 	TRACE_ERROR(("udf_get_vnode: InitCheck failed\n"));
200 	delete icb;
201 	return B_ERROR;
202 }
203 
204 
205 //	#pragma mark - fs_vnode_ops functions
206 
207 
208 static status_t
209 udf_lookup(fs_volume *_volume, fs_vnode *_directory, const char *file,
210 	ino_t *vnodeID)
211 {
212 	TRACE(("udf_lookup: _directory = %p, filename = %s\n", _directory, file));
213 
214 	Volume *volume = (Volume *)_volume->private_volume;
215 	Icb *dir = (Icb *)_directory->private_node;
216 	Icb *node = NULL;
217 
218 	status_t status = B_OK;
219 
220 	if (strcmp(file, ".") == 0) {
221 		TRACE(("udf_lookup: file = ./\n"));
222 		*vnodeID = dir->Id();
223 		status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&node);
224 		if (status != B_OK)
225 			return B_ENTRY_NOT_FOUND;
226 	} else {
227 		status = dir->Find(file, vnodeID);
228 		if (status != B_OK)
229 			return status;
230 
231 		Icb *icb;
232 		status = get_vnode(volume->FSVolume(), *vnodeID, (void **)&icb);
233 		if (status != B_OK)
234 			return B_ENTRY_NOT_FOUND;
235 	}
236 	TRACE(("udf_lookup: vnodeId = %Ld found!\n", *vnodeID));
237 
238 	return B_OK;
239 }
240 
241 
242 static status_t
243 udf_put_vnode(fs_volume *volume, fs_vnode *node, bool reenter)
244 {
245 	TRACE(("udf_put_vnode: volume = %p, node = %p\n", volume, node));
246 // No debug-to-file in release_vnode; can cause a deadlock in
247 // rare circumstances.
248 #if !DEBUG_TO_FILE
249 	DEBUG_INIT_ETC(NULL, ("node: %p", node));
250 #endif
251 	Icb *icb = (Icb *)node->private_node;
252 	delete icb;
253 #if !DEBUG_TO_FILE
254 	RETURN(B_OK);
255 #else
256 	return B_OK;
257 #endif
258 }
259 
260 
261 static status_t
262 udf_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
263 {
264 	TRACE(("udf_remove_vnode: _volume = %p, _node = %p\n", _volume, _node));
265 	return B_ERROR;
266 }
267 
268 
269 static status_t
270 udf_read_stat(fs_volume *_volume, fs_vnode *node, struct stat *stat)
271 {
272 	TRACE(("udf_read_stat: _volume = %p, node = %p\n", _volume, node));
273 
274 	if (!_volume || !node || !stat)
275 		return B_BAD_VALUE;
276 
277 	Volume *volume = (Volume *)_volume->private_volume;
278 	Icb *icb = (Icb *)node->private_node;
279 
280 	stat->st_dev = volume->ID();
281 	stat->st_ino = icb->Id();
282 	stat->st_nlink = icb->FileLinkCount();
283 	stat->st_blksize = volume->BlockSize();
284 
285 	TRACE(("udf_read_stat: st_dev = %ld, st_ino = %Ld, st_blksize = %d\n",
286 		stat->st_dev, stat->st_ino, stat->st_blksize));
287 
288 	stat->st_uid = icb->Uid();
289 	stat->st_gid = icb->Gid();
290 
291 	stat->st_mode = icb->Mode();
292 	stat->st_size = icb->Length();
293 	stat->st_blocks = (stat->st_size + 511) / 512;
294 
295 	// File times. For now, treat the modification time as creation
296 	// time as well, since true creation time is an optional extended
297 	// attribute, and supporting EAs is going to be a PITA. ;-)
298 	icb->GetAccessTime(stat->st_atim);
299 	icb->GetModificationTime(stat->st_mtim);
300 	icb->GetModificationTime(stat->st_ctim);
301 	icb->GetModificationTime(stat->st_crtim);
302 	//icb->GetChangeTime(stat->st_ctim);
303 	//icb->GetCreationTime(stat->st_crtim);
304 
305 	TRACE(("udf_read_stat: mode = 0x%x, st_ino: %Ld\n", stat->st_mode,
306 		stat->st_ino));
307 
308 	return B_OK;
309 }
310 
311 
312 static status_t
313 udf_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie)
314 {
315 	TRACE(("udf_open: _volume = %p, _node = %p\n", _volume, _node));
316 	return B_OK;
317 }
318 
319 
320 static status_t
321 udf_close(fs_volume* _volume, fs_vnode* _node, void* _cookie)
322 {
323 	TRACE(("udf_close: _volume = %p, _node = %p\n", _volume, _node));
324 	return B_OK;
325 }
326 
327 
328 static status_t
329 udf_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
330 {
331 	TRACE(("udf_free_cookie: _volume = %p, _node = %p\n", _volume, _node));
332 	return B_OK;
333 }
334 
335 
336 static status_t
337 udf_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
338 {
339 	TRACE(("udf_access: _volume = %p, _node = %p\n", _volume, _node));
340 	return B_OK;
341 }
342 
343 
344 static status_t
345 udf_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
346 	void *buffer, size_t *length)
347 {
348 	TRACE(("udf_read: ID = %ld, pos = %lld, length = %lu\n",
349 		((Volume *)volume->private_volume)->ID(), pos, *length));
350 
351 	Icb *icb = (Icb *)vnode->private_node;
352 	DEBUG_INIT_ETC("udf_read", ("ID = %ld, pos = %lld, length = %lu",
353 		((Volume *)volume->private_volume)->ID(), pos, *length));
354 
355 //	if (!inode->HasUserAccessableStream()) {
356 //		*_length = 0;
357 //		RETURN_ERROR(B_BAD_VALUE);
358 //	}
359 
360 	RETURN(icb->Read(pos, buffer, length));
361 }
362 
363 
364 static status_t
365 udf_io(fs_volume *volume, fs_vnode *vnode, void *cookie, io_request *request)
366 {
367 	if (io_request_is_write(request)) {
368 		notify_io_request(request, B_READ_ONLY_DEVICE);
369 		return B_READ_ONLY_DEVICE;
370 	}
371 
372 	Icb *icb = (Icb *)vnode->private_node;
373 	if (icb->FileCache() == NULL) {
374 		notify_io_request(request, B_BAD_VALUE);
375 		return B_BAD_VALUE;
376 	}
377 
378 	return do_iterative_fd_io(((Volume *)volume->private_volume)->Device(),
379 		request, iterative_io_get_vecs_hook, iterative_io_finished_hook, icb);
380 }
381 
382 
383 static status_t
384 udf_get_file_map(fs_volume *_volume, fs_vnode *vnode, off_t offset, size_t size,
385 	struct file_io_vec *vecs, size_t *count)
386 {
387 	Icb *icb = (Icb *)vnode->private_node;
388 	return icb->GetFileMap(offset, size, vecs, count);
389 }
390 
391 
392 static status_t
393 udf_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie)
394 {
395 	TRACE(("udf_open_dir: volume = %p, vnode = %p\n", volume, vnode));
396 	DEBUG_INIT_ETC("udf_open_dir", ("ID = %ld",
397 		((Volume *)volume->private_volume)->ID()));
398 
399 	if (!volume || !vnode || !cookie)
400 		RETURN(B_BAD_VALUE);
401 
402 	Icb *dir = (Icb *)vnode->private_node;
403 
404 	if (!dir->IsDirectory()) {
405 		TRACE_ERROR(("udf_open_dir: given Icb is not a directory (type: %d)\n",
406 			dir->Type()));
407 		return B_NOT_A_DIRECTORY;
408 	}
409 
410 	DirectoryIterator *iterator = NULL;
411 	status_t status = dir->GetDirectoryIterator(&iterator);
412 	if (status != B_OK) {
413 		TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%"
414 			B_PRIx32 ", `%s'\n", status, strerror(status)));
415 		return status;
416 	}
417 	*cookie = (void *)iterator;
418 	TRACE(("udf_open_dir: *cookie = %p\n", *cookie));
419 
420 	return B_OK;
421 }
422 
423 
424 static status_t
425 udf_close_dir(fs_volume *_volume, fs_vnode *node, void *_cookie)
426 {
427 	TRACE(("udf_close_dir: _volume = %p, node = %p\n", _volume, node));
428 	return B_OK;
429 }
430 
431 
432 static status_t
433 udf_free_dir_cookie(fs_volume *_volume, fs_vnode *node, void *_cookie)
434 {
435 	TRACE(("udf_free_dir_cookie: _volume = %p, node = %p\n", _volume, node));
436 	return B_OK;
437 }
438 
439 
440 static status_t
441 udf_read_dir(fs_volume *_volume, fs_vnode *vnode, void *cookie,
442 	struct dirent *dirent, size_t bufferSize, uint32 *_num)
443 {
444 	TRACE(("udf_read_dir: _volume = %p, vnode = %p, bufferSize = %ld\n",
445 		_volume, vnode, bufferSize));
446 
447 	if (!_volume || !vnode || !cookie || !_num
448 			|| bufferSize < sizeof(struct dirent)) {
449 		return B_BAD_VALUE;
450 	}
451 
452 	Volume *volume = (Volume *)_volume->private_volume;
453 	Icb *dir = (Icb *)vnode->private_node;
454 	DirectoryIterator *iterator = (DirectoryIterator *)cookie;
455 
456 	DEBUG_INIT_ETC("udf_read_dir", ("ID = %ld", volume->ID()));
457 
458 	if (dir != iterator->Parent()) {
459 		TRACE_ERROR(("udf_read_dir: Icb does not match parent Icb of given "
460 			"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
461 		return B_BAD_VALUE;
462 	}
463 
464 	uint32 nameLength = bufferSize - sizeof(struct dirent) + 1;
465 	ino_t id;
466 	status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id);
467 	if (!status) {
468 		TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength));
469 		*_num = 1;
470 		dirent->d_dev = volume->ID();
471 		dirent->d_ino = id;
472 		dirent->d_reclen = sizeof(struct dirent) + nameLength - 1;
473 	} else {
474 		*_num = 0;
475 		// Clear the status for end of directory
476 		if (status == B_ENTRY_NOT_FOUND)
477 			status = B_OK;
478 	}
479 
480 	RETURN(status);
481 }
482 
483 
484 status_t
485 udf_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
486 {
487 	TRACE(("udf_rewind_dir: volume = %p, vnode = %p, cookie = %p\n",
488 		volume, vnode, cookie));
489 	DEBUG_INIT_ETC("udf_rewind_dir", ("ID = %ld",
490 		((Volume *)volume->private_volume)->ID()));
491 
492 	if (!volume || !vnode || !cookie)
493 		RETURN(B_BAD_VALUE);
494 
495 	Icb *dir = (Icb *)vnode->private_node;
496 	DirectoryIterator *iterator = (DirectoryIterator *)cookie;
497 
498 	if (dir != iterator->Parent()) {
499 		PRINT(("udf_rewind_dir: icb does not match parent Icb of given "
500 			"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
501 		return B_BAD_VALUE;
502 	}
503 
504 	iterator->Rewind();
505 
506 	return B_OK;
507 }
508 
509 
510 //	#pragma mark -
511 
512 
513 /*! \brief mount
514 
515 	\todo I'm using the B_GET_GEOMETRY ioctl() to find out where the end of the
516 	      partition is. This won't work for handling multi-session semantics correctly.
517 	      To support them correctly in R5 I need either:
518 	      - A way to get the proper info (best)
519 	      - To ignore trying to find anchor volume descriptor pointers at
520 	        locations N-256 and N. (acceptable, perhaps, but not really correct)
521 	      Either way we should address this problem properly for OBOS::R1.
522 	\todo Looks like B_GET_GEOMETRY doesn't work on non-device files (i.e.
523 	      disk images), so I need to use stat or something else for those
524 	      instances.
525 */
526 static status_t
527 udf_mount(fs_volume *_volume, const char *_device, uint32 flags,
528 	const char *args, ino_t *_rootVnodeID)
529 {
530 	TRACE(("udf_mount: device = %s\n", _device));
531 	status_t status = B_OK;
532 	Volume *volume = NULL;
533 	off_t deviceOffset = 0;
534 	off_t numBlock = 0;
535 	partition_info info;
536 	device_geometry geometry;
537 
538 	// Here we need to figure out the length of the device, and if we're
539 	// attempting to open a multisession volume, we need to figure out the
540 	// offset into the raw disk at which the volume begins, then open
541 	// the raw volume itself instead of the fake partition device the
542 	// kernel gives us, since multisession UDF volumes are allowed to access
543 	// the data in their own partition, as well as the data in any partitions
544 	// that precede them physically on the disc.
545 	int device = open(_device, O_RDONLY);
546 	status = device < B_OK ? device : B_OK;
547 	if (!status) {
548 		// First try to treat the device like a special partition device. If that's
549 		// what we have, then we can use the partition_info data to figure out the
550 		// name of the raw device (which we'll open instead), the offset into the
551 		// raw device at which the volume of interest will begin, and the total
552 		// length from the beginning of the raw device that we're allowed to access.
553 		//
554 		// If that fails, then we try to treat the device as an actual raw device,
555 		// and see if we can get the device size with B_GET_GEOMETRY syscall, since
556 		// stat()ing a raw device appears to not work.
557 		//
558 		// Finally, if that also fails, we're probably stuck with trying to mount
559 		// a regular file, so we just stat() it to get the device size.
560 		//
561 		// If that fails, you're just SOL.
562 
563 		if (ioctl(device, B_GET_PARTITION_INFO, &info, sizeof(partition_info)) == 0) {
564 			TRACE(("partition_info:\n"));
565 			TRACE(("\toffset:             %Ld\n", info.offset));
566 			TRACE(("\tsize:               %Ld\n", info.size));
567 			TRACE(("\tlogical_block_size: %ld\n", info.logical_block_size));
568 			TRACE(("\tsession:            %ld\n", info.session));
569 			TRACE(("\tpartition:          %ld\n", info.partition));
570 			TRACE(("\tdevice:             `%s'\n", info.device));
571 			_device = info.device;
572 			deviceOffset = info.offset / info.logical_block_size;
573 			numBlock = deviceOffset + info.size / info.logical_block_size;
574 		} else if (ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(device_geometry)) == 0) {
575 			TRACE(("geometry_info:\n"));
576 			TRACE(("\tsectors_per_track: %ld\n", geometry.sectors_per_track));
577 			TRACE(("\tcylinder_count:    %ld\n", geometry.cylinder_count));
578 			TRACE(("\thead_count:        %ld\n", geometry.head_count));
579 			deviceOffset = 0;
580 			numBlock = (off_t)geometry.sectors_per_track
581 				* geometry.cylinder_count * geometry.head_count;
582 		} else {
583 			struct stat stat;
584 			status = fstat(device, &stat) < 0 ? B_ERROR : B_OK;
585 			if (!status) {
586 				TRACE(("stat_info:\n"));
587 				TRACE(("\tst_size: %Ld\n", stat.st_size));
588 				deviceOffset = 0;
589 				numBlock = stat.st_size / 2048;
590 			}
591 		}
592 		// Close the device
593 		close(device);
594 	}
595 
596 	// Create and mount the volume
597 	volume = new(std::nothrow) Volume(_volume);
598 	status = volume->Mount(_device, deviceOffset, numBlock, 2048, flags);
599 	if (status != B_OK) {
600 		delete volume;
601 		return status;
602 	}
603 
604 	_volume->private_volume = volume;
605 	_volume->ops = &gUDFVolumeOps;
606 	*_rootVnodeID = volume->RootIcb()->Id();
607 
608 	TRACE(("udf_mount: succefully mounted the partition\n"));
609 	return B_OK;
610 }
611 
612 
613 //	#pragma mark -
614 
615 
616 static status_t
617 udf_std_ops(int32 op, ...)
618 {
619 	switch (op) {
620 		case B_MODULE_INIT:
621 			init_entities();
622 			return B_OK;
623 		case B_MODULE_UNINIT:
624 			return B_OK;
625 		default:
626 			return B_ERROR;
627 	}
628 }
629 
630 fs_volume_ops gUDFVolumeOps = {
631 	&udf_unmount,
632 	&udf_read_fs_stat,
633 	NULL,	// write_fs_stat
634 	NULL,	// sync
635 	&udf_get_vnode,
636 
637 	/* index directory & index operations */
638 	NULL,	// open_index_dir
639 	NULL,	// close_index_dir
640 	NULL,	// free_index_dir_cookie
641 	NULL,	// read_index_dir
642 	NULL,	// rewind_index_dir
643 	NULL,	// create_index
644 	NULL,	// remove_index
645 	NULL,	// read_index_stat
646 
647 	/* query operations */
648 	NULL,	// open_query
649 	NULL,	// close_query
650 	NULL,	// free_query_cookie
651 	NULL,	// read_query
652 	NULL,	// rewind_query
653 
654 	/* support for FS layers */
655 	NULL,	// create_sub_vnode
656 	NULL,	// delete_sub_vnode
657 };
658 
659 fs_vnode_ops gUDFVnodeOps = {
660 	/* vnode operatoins */
661 	&udf_lookup,
662 	NULL,	// get_vnode_name
663 	&udf_put_vnode,
664 	&udf_remove_vnode,
665 
666 	/* VM file access */
667 	NULL,	// can_page
668 	NULL,	// read_pages
669 	NULL,	// write_pages
670 
671 	/* asynchronous I/O */
672 	&udf_io,
673 	NULL,	// cancel_io()
674 
675 	/* cache file access */
676 	&udf_get_file_map,
677 
678 	/* common operations */
679 	NULL,	// ioctl
680 	NULL,	// set_flags
681 	NULL,	// select
682 	NULL,	// deselect
683 	NULL,	// fsync
684 	NULL,	// read_symlink
685 	NULL,	// create_symlnk
686 	NULL,	// link
687 	NULL,	// unlink
688 	NULL,	// rename
689 	&udf_access,
690 	&udf_read_stat,
691 	NULL,	// write_stat
692 	NULL,	// preallocate
693 
694 	/* file operations */
695 	NULL,	// create
696 	&udf_open,
697 	&udf_close,
698 	&udf_free_cookie,
699 	&udf_read,
700 	NULL,	// write
701 
702 	/* directory operations */
703 	NULL,	// create_dir
704 	NULL,	// remove_dir
705 	&udf_open_dir,
706 	&udf_close_dir,
707 	&udf_free_dir_cookie,
708 	&udf_read_dir,
709 	&udf_rewind_dir,
710 
711 	/* attribue directory operations */
712 	NULL,	// open_attr_dir
713 	NULL,	// close_attr_dir
714 	NULL,	// free_attr_dir_cookie
715 	NULL,	// read_attr_dir
716 	NULL,	// rewind_attr_dir
717 
718 	/* attribute operations */
719 	NULL,	// create_attr
720 	NULL,	// open_attr
721 	NULL,	// close_attr
722 	NULL,	// free_attr_cookie
723 	NULL,	// read_attr
724 	NULL,	// write_attr
725 	NULL,	// read_attr_stat
726 	NULL,	// write_attr_stat
727 	NULL,	// rename_attr
728 	NULL,	// remove_attr
729 
730 	/* support for node and FS layers */
731 	NULL,	// create_special_node
732 	NULL	// get_super_vnode
733 
734 };
735 
736 static file_system_module_info sUDFFileSystem = {
737 	{
738 		"file_systems/udf" B_CURRENT_FS_API_VERSION,
739 		0,
740 		udf_std_ops,
741 	},
742 
743 	"udf",					// short_name
744 	"UDF File System",		// pretty_name
745 	0, // DDM flags
746 
747 	&udf_identify_partition,
748 	&udf_scan_partition,
749 	&udf_free_identify_partition_cookie,
750 	NULL,	// free_partition_content_cookie()
751 
752 	&udf_mount,
753 
754 	NULL,
755 };
756 
757 module_info *modules[] = {
758 	(module_info *)&sUDFFileSystem,
759 	NULL,
760 };
761