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 = %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
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 = %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
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 = %Ld 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 = %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
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 = %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
364static status_t
365udf_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
383static status_t
384udf_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
392static status_t
393udf_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%lx, "
414			"`%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
424static status_t
425udf_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
432static status_t
433udf_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
440static status_t
441udf_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
484status_t
485udf_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*/
526static status_t
527udf_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) == 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) == 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
616static status_t
617udf_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
630fs_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
659fs_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
736static 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
757module_info *modules[] = {
758	(module_info *)&sUDFFileSystem,
759	NULL,
760};
761