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