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
42extern fs_volume_ops gUDFVolumeOps;
43extern fs_vnode_ops gUDFVnodeOps;
44
45
46struct identify_cookie {
47	struct logical_volume_descriptor logical_volume_descriptor;
48};
49
50
51//	#pragma mark - io callbacks
52
53
54static status_t
55iterative_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
64static status_t
65iterative_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
76static float
77udf_identify_partition(int fd, partition_data *partition, void **_cookie)
78{
79	TRACE(("udf_identify_partition: fd = %d, id = %ld, offset = %lld, size = %lld "
80		"content_size = %lld, 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
105static status_t
106udf_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
128static void
129udf_free_identify_partition_cookie(partition_data *partition, void *cookie)
130{
131	delete (identify_cookie *)cookie;
132}
133
134
135static status_t
136udf_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
145static status_t
146udf_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
172static status_t
173udf_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 = %lld, 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
208static status_t
209udf_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 = %lld found!\n", *vnodeID));
237
238	return B_OK;
239}
240
241
242static status_t
243udf_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
261static status_t
262udf_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
269static status_t
270udf_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 = %lld, 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: %lld\n", stat->st_mode,
306		stat->st_ino));
307
308	return B_OK;
309}
310
311
312static status_t
313udf_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
320static status_t
321udf_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
328static status_t
329udf_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
336static status_t
337udf_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
344static status_t
345udf_read(fs_volume *volume, fs_vnode *vnode, void *cookie, off_t pos,
346	void *buffer, size_t *length)
347{
348	TRACE(("udf_read: ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", "
349			"length = %lu\n",
350		((Volume *)volume->private_volume)->ID(), pos, *length));
351
352	Icb *icb = (Icb *)vnode->private_node;
353	DEBUG_INIT_ETC("udf_read", ("ID = %" B_PRIdDEV ", pos = %" B_PRIdOFF ", "
354			"length = %lu",
355		((Volume *)volume->private_volume)->ID(), pos, *length));
356
357//	if (!inode->HasUserAccessableStream()) {
358//		*_length = 0;
359//		RETURN_ERROR(B_BAD_VALUE);
360//	}
361
362	RETURN(icb->Read(pos, buffer, length));
363}
364
365
366static status_t
367udf_io(fs_volume *volume, fs_vnode *vnode, void *cookie, io_request *request)
368{
369	if (io_request_is_write(request)) {
370		notify_io_request(request, B_READ_ONLY_DEVICE);
371		return B_READ_ONLY_DEVICE;
372	}
373
374	Icb *icb = (Icb *)vnode->private_node;
375	if (icb->FileCache() == NULL) {
376		notify_io_request(request, B_BAD_VALUE);
377		return B_BAD_VALUE;
378	}
379
380	return do_iterative_fd_io(((Volume *)volume->private_volume)->Device(),
381		request, iterative_io_get_vecs_hook, iterative_io_finished_hook, icb);
382}
383
384
385static status_t
386udf_get_file_map(fs_volume *_volume, fs_vnode *vnode, off_t offset, size_t size,
387	struct file_io_vec *vecs, size_t *count)
388{
389	Icb *icb = (Icb *)vnode->private_node;
390	return icb->GetFileMap(offset, size, vecs, count);
391}
392
393
394static status_t
395udf_open_dir(fs_volume *volume, fs_vnode *vnode, void **cookie)
396{
397	TRACE(("udf_open_dir: volume = %p, vnode = %p\n", volume, vnode));
398	DEBUG_INIT_ETC("udf_open_dir", ("ID = %" B_PRIdDEV,
399		((Volume *)volume->private_volume)->ID()));
400
401	if (!volume || !vnode || !cookie)
402		RETURN(B_BAD_VALUE);
403
404	Icb *dir = (Icb *)vnode->private_node;
405
406	if (!dir->IsDirectory()) {
407		TRACE_ERROR(("udf_open_dir: given Icb is not a directory (type: %d)\n",
408			dir->Type()));
409		return B_NOT_A_DIRECTORY;
410	}
411
412	DirectoryIterator *iterator = NULL;
413	status_t status = dir->GetDirectoryIterator(&iterator);
414	if (status != B_OK) {
415		TRACE_ERROR(("udf_open_dir: error getting directory iterator: 0x%"
416			B_PRIx32 ", `%s'\n", status, strerror(status)));
417		return status;
418	}
419	*cookie = (void *)iterator;
420	TRACE(("udf_open_dir: *cookie = %p\n", *cookie));
421
422	return B_OK;
423}
424
425
426static status_t
427udf_close_dir(fs_volume *_volume, fs_vnode *node, void *_cookie)
428{
429	TRACE(("udf_close_dir: _volume = %p, node = %p\n", _volume, node));
430	return B_OK;
431}
432
433
434static status_t
435udf_free_dir_cookie(fs_volume *_volume, fs_vnode *node, void *_cookie)
436{
437	TRACE(("udf_free_dir_cookie: _volume = %p, node = %p\n", _volume, node));
438	return B_OK;
439}
440
441
442static status_t
443udf_read_dir(fs_volume *_volume, fs_vnode *vnode, void *cookie,
444	struct dirent *dirent, size_t bufferSize, uint32 *_num)
445{
446	TRACE(("udf_read_dir: _volume = %p, vnode = %p, bufferSize = %ld\n",
447		_volume, vnode, bufferSize));
448
449	if (!_volume || !vnode || !cookie || !_num
450			|| bufferSize < sizeof(struct dirent)) {
451		return B_BAD_VALUE;
452	}
453
454	Volume *volume = (Volume *)_volume->private_volume;
455	Icb *dir = (Icb *)vnode->private_node;
456	DirectoryIterator *iterator = (DirectoryIterator *)cookie;
457
458	DEBUG_INIT_ETC("udf_read_dir", ("ID = %" B_PRIdDEV , volume->ID()));
459
460	if (dir != iterator->Parent()) {
461		TRACE_ERROR(("udf_read_dir: Icb does not match parent Icb of given "
462			"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
463		return B_BAD_VALUE;
464	}
465
466	uint32 nameLength = bufferSize - offsetof(struct dirent, d_name);
467	ino_t id;
468	status_t status = iterator->GetNextEntry(dirent->d_name, &nameLength, &id);
469	if (!status) {
470		TRACE(("udf_read_dir: dirent->d_name = %s, length = %ld\n", dirent->d_name, nameLength));
471		*_num = 1;
472		dirent->d_dev = volume->ID();
473		dirent->d_ino = id;
474		dirent->d_reclen = offsetof(struct dirent, d_name) + nameLength + 1;
475	} else {
476		*_num = 0;
477		// Clear the status for end of directory
478		if (status == B_ENTRY_NOT_FOUND)
479			status = B_OK;
480	}
481
482	RETURN(status);
483}
484
485
486status_t
487udf_rewind_dir(fs_volume *volume, fs_vnode *vnode, void *cookie)
488{
489	TRACE(("udf_rewind_dir: volume = %p, vnode = %p, cookie = %p\n",
490		volume, vnode, cookie));
491	DEBUG_INIT_ETC("udf_rewind_dir", ("ID = %" B_PRIdDEV,
492		((Volume *)volume->private_volume)->ID()));
493
494	if (!volume || !vnode || !cookie)
495		RETURN(B_BAD_VALUE);
496
497	Icb *dir = (Icb *)vnode->private_node;
498	DirectoryIterator *iterator = (DirectoryIterator *)cookie;
499
500	if (dir != iterator->Parent()) {
501		PRINT(("udf_rewind_dir: icb does not match parent Icb of given "
502			"DirectoryIterator! (iterator->Parent = %p)\n", iterator->Parent()));
503		return B_BAD_VALUE;
504	}
505
506	iterator->Rewind();
507
508	return B_OK;
509}
510
511
512//	#pragma mark -
513
514
515/*! \brief mount
516
517	\todo I'm using the B_GET_GEOMETRY ioctl() to find out where the end of the
518	      partition is. This won't work for handling multi-session semantics correctly.
519	      To support them correctly in R5 I need either:
520	      - A way to get the proper info (best)
521	      - To ignore trying to find anchor volume descriptor pointers at
522	        locations N-256 and N. (acceptable, perhaps, but not really correct)
523	      Either way we should address this problem properly for Haiku::R1.
524	\todo Looks like B_GET_GEOMETRY doesn't work on non-device files (i.e.
525	      disk images), so I need to use stat or something else for those
526	      instances.
527*/
528static status_t
529udf_mount(fs_volume *_volume, const char *_device, uint32 flags,
530	const char *args, ino_t *_rootVnodeID)
531{
532	TRACE(("udf_mount: device = %s\n", _device));
533	status_t status = B_OK;
534	Volume *volume = NULL;
535	off_t deviceOffset = 0;
536	off_t numBlock = 0;
537	partition_info info;
538	device_geometry geometry;
539
540	// Here we need to figure out the length of the device, and if we're
541	// attempting to open a multisession volume, we need to figure out the
542	// offset into the raw disk at which the volume begins, then open
543	// the raw volume itself instead of the fake partition device the
544	// kernel gives us, since multisession UDF volumes are allowed to access
545	// the data in their own partition, as well as the data in any partitions
546	// that precede them physically on the disc.
547	int device = open(_device, O_RDONLY);
548	status = device < B_OK ? device : B_OK;
549	if (!status) {
550		// First try to treat the device like a special partition device. If that's
551		// what we have, then we can use the partition_info data to figure out the
552		// name of the raw device (which we'll open instead), the offset into the
553		// raw device at which the volume of interest will begin, and the total
554		// length from the beginning of the raw device that we're allowed to access.
555		//
556		// If that fails, then we try to treat the device as an actual raw device,
557		// and see if we can get the device size with B_GET_GEOMETRY syscall, since
558		// stat()ing a raw device appears to not work.
559		//
560		// Finally, if that also fails, we're probably stuck with trying to mount
561		// a regular file, so we just stat() it to get the device size.
562		//
563		// If that fails, you're just SOL.
564
565		if (ioctl(device, B_GET_PARTITION_INFO, &info, sizeof(partition_info)) == 0) {
566			TRACE(("partition_info:\n"));
567			TRACE(("\toffset:             %lld\n", info.offset));
568			TRACE(("\tsize:               %lld\n", info.size));
569			TRACE(("\tlogical_block_size: %ld\n", info.logical_block_size));
570			TRACE(("\tsession:            %ld\n", info.session));
571			TRACE(("\tpartition:          %ld\n", info.partition));
572			TRACE(("\tdevice:             `%s'\n", info.device));
573			_device = info.device;
574			deviceOffset = info.offset / info.logical_block_size;
575			numBlock = deviceOffset + info.size / info.logical_block_size;
576		} else if (ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(device_geometry)) == 0) {
577			TRACE(("geometry_info:\n"));
578			TRACE(("\tsectors_per_track: %ld\n", geometry.sectors_per_track));
579			TRACE(("\tcylinder_count:    %ld\n", geometry.cylinder_count));
580			TRACE(("\thead_count:        %ld\n", geometry.head_count));
581			deviceOffset = 0;
582			numBlock = (off_t)geometry.sectors_per_track
583				* geometry.cylinder_count * geometry.head_count;
584		} else {
585			struct stat stat;
586			status = fstat(device, &stat) < 0 ? B_ERROR : B_OK;
587			if (!status) {
588				TRACE(("stat_info:\n"));
589				TRACE(("\tst_size: %lld\n", stat.st_size));
590				deviceOffset = 0;
591				numBlock = stat.st_size / 2048;
592			}
593		}
594		// Close the device
595		close(device);
596	}
597
598	// Create and mount the volume
599	volume = new(std::nothrow) Volume(_volume);
600	status = volume->Mount(_device, deviceOffset, numBlock, 2048, flags);
601	if (status != B_OK) {
602		delete volume;
603		return status;
604	}
605
606	_volume->private_volume = volume;
607	_volume->ops = &gUDFVolumeOps;
608	*_rootVnodeID = volume->RootIcb()->Id();
609
610	TRACE(("udf_mount: succefully mounted the partition\n"));
611	return B_OK;
612}
613
614
615//	#pragma mark -
616
617
618static status_t
619udf_std_ops(int32 op, ...)
620{
621	switch (op) {
622		case B_MODULE_INIT:
623			init_entities();
624			return B_OK;
625		case B_MODULE_UNINIT:
626			return B_OK;
627		default:
628			return B_ERROR;
629	}
630}
631
632fs_volume_ops gUDFVolumeOps = {
633	&udf_unmount,
634	&udf_read_fs_stat,
635	NULL,	// write_fs_stat
636	NULL,	// sync
637	&udf_get_vnode,
638
639	/* index directory & index operations */
640	NULL,	// open_index_dir
641	NULL,	// close_index_dir
642	NULL,	// free_index_dir_cookie
643	NULL,	// read_index_dir
644	NULL,	// rewind_index_dir
645	NULL,	// create_index
646	NULL,	// remove_index
647	NULL,	// read_index_stat
648
649	/* query operations */
650	NULL,	// open_query
651	NULL,	// close_query
652	NULL,	// free_query_cookie
653	NULL,	// read_query
654	NULL,	// rewind_query
655
656	/* support for FS layers */
657	NULL,	// create_sub_vnode
658	NULL,	// delete_sub_vnode
659};
660
661fs_vnode_ops gUDFVnodeOps = {
662	/* vnode operatoins */
663	&udf_lookup,
664	NULL,	// get_vnode_name
665	&udf_put_vnode,
666	&udf_remove_vnode,
667
668	/* VM file access */
669	NULL,	// can_page
670	NULL,	// read_pages
671	NULL,	// write_pages
672
673	/* asynchronous I/O */
674	&udf_io,
675	NULL,	// cancel_io()
676
677	/* cache file access */
678	&udf_get_file_map,
679
680	/* common operations */
681	NULL,	// ioctl
682	NULL,	// set_flags
683	NULL,	// select
684	NULL,	// deselect
685	NULL,	// fsync
686	NULL,	// read_symlink
687	NULL,	// create_symlnk
688	NULL,	// link
689	NULL,	// unlink
690	NULL,	// rename
691	&udf_access,
692	&udf_read_stat,
693	NULL,	// write_stat
694	NULL,	// preallocate
695
696	/* file operations */
697	NULL,	// create
698	&udf_open,
699	&udf_close,
700	&udf_free_cookie,
701	&udf_read,
702	NULL,	// write
703
704	/* directory operations */
705	NULL,	// create_dir
706	NULL,	// remove_dir
707	&udf_open_dir,
708	&udf_close_dir,
709	&udf_free_dir_cookie,
710	&udf_read_dir,
711	&udf_rewind_dir,
712
713	/* attribue directory operations */
714	NULL,	// open_attr_dir
715	NULL,	// close_attr_dir
716	NULL,	// free_attr_dir_cookie
717	NULL,	// read_attr_dir
718	NULL,	// rewind_attr_dir
719
720	/* attribute operations */
721	NULL,	// create_attr
722	NULL,	// open_attr
723	NULL,	// close_attr
724	NULL,	// free_attr_cookie
725	NULL,	// read_attr
726	NULL,	// write_attr
727	NULL,	// read_attr_stat
728	NULL,	// write_attr_stat
729	NULL,	// rename_attr
730	NULL,	// remove_attr
731
732	/* support for node and FS layers */
733	NULL,	// create_special_node
734	NULL	// get_super_vnode
735
736};
737
738static file_system_module_info sUDFFileSystem = {
739	{
740		"file_systems/udf" B_CURRENT_FS_API_VERSION,
741		0,
742		udf_std_ops,
743	},
744
745	"udf",					// short_name
746	"UDF File System",		// pretty_name
747	0, // DDM flags
748
749	&udf_identify_partition,
750	&udf_scan_partition,
751	&udf_free_identify_partition_cookie,
752	NULL,	// free_partition_content_cookie()
753
754	&udf_mount,
755
756	NULL,
757};
758
759module_info *modules[] = {
760	(module_info *)&sUDFFileSystem,
761	NULL,
762};
763