xref: /haiku/src/add-ons/kernel/file_systems/exfat/kernel_interface.cpp (revision 6f80a9801fedbe7355c4360bd204ba746ec3ec2d)
1 /*
2  * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de.
3  * Copyright 2011, Jérôme Duval, korli@users.berlios.de.
4  * Copyright 2014 Haiku, Inc. All rights reserved.
5  *
6  * Distributed under the terms of the MIT License.
7  *
8  * Authors:
9  *		Axel Dörfler, axeld@pinc-software.de
10  *		Jérôme Duval, korli@users.berlios.de
11  *		John Scipione, jscipione@gmail.com
12  */
13 
14 
15 #include <dirent.h>
16 #include <unistd.h>
17 #include <util/kernel_cpp.h>
18 #include <string.h>
19 
20 #include <new>
21 
22 #include <AutoDeleter.h>
23 #include <fs_cache.h>
24 #include <fs_info.h>
25 #include <io_requests.h>
26 #include <NodeMonitor.h>
27 #include <StorageDefs.h>
28 #include <util/AutoLock.h>
29 
30 #include "DirectoryIterator.h"
31 #include "exfat.h"
32 #include "Inode.h"
33 #include "Utility.h"
34 
35 
36 //#define TRACE_EXFAT
37 #ifdef TRACE_EXFAT
38 #	define TRACE(x...) dprintf("\33[34mexfat:\33[0m " x)
39 #else
40 #	define TRACE(x...) ;
41 #endif
42 #define ERROR(x...) dprintf("\33[34mexfat:\33[0m " x)
43 
44 
45 #define EXFAT_IO_SIZE	65536
46 
47 
48 struct identify_cookie {
49 	exfat_super_block super_block;
50 	char name[B_FILE_NAME_LENGTH];
51 };
52 
53 
54 //!	exfat_io() callback hook
55 static status_t
56 iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset,
57 	size_t size, struct file_io_vec* vecs, size_t* _count)
58 {
59 	Inode* inode = (Inode*)cookie;
60 
61 	return file_map_translate(inode->Map(), offset, size, vecs, _count,
62 		inode->GetVolume()->BlockSize());
63 }
64 
65 
66 //!	exfat_io() callback hook
67 static status_t
68 iterative_io_finished_hook(void* cookie, io_request* request, status_t status,
69 	bool partialTransfer, size_t bytesTransferred)
70 {
71 	Inode* inode = (Inode*)cookie;
72 	rw_lock_read_unlock(inode->Lock());
73 	return B_OK;
74 }
75 
76 
77 //	#pragma mark - Scanning
78 
79 
80 static float
81 exfat_identify_partition(int fd, partition_data* partition, void** _cookie)
82 {
83 	struct exfat_super_block superBlock;
84 	status_t status = Volume::Identify(fd, &superBlock);
85 	if (status != B_OK)
86 		return -1;
87 
88 	identify_cookie* cookie = new (std::nothrow)identify_cookie;
89 	if (cookie == NULL)
90 		return -1;
91 
92 	memcpy(&cookie->super_block, &superBlock, sizeof(exfat_super_block));
93 	memset(cookie->name, 0, sizeof(cookie->name));
94 		// zero out volume name
95 
96 	uint32 rootDirCluster = superBlock.RootDirCluster();
97 	uint32 blockSize = 1 << superBlock.BlockShift();
98 	uint32 clusterSize = blockSize << superBlock.BlocksPerClusterShift();
99 	uint64 rootDirectoryOffset = EXFAT_SUPER_BLOCK_OFFSET
100 		+ (uint64)superBlock.FirstDataBlock() * blockSize
101 		+ (rootDirCluster - 2) * clusterSize;
102 	struct exfat_entry entry;
103 	size_t entrySize = sizeof(struct exfat_entry);
104 	for (uint32 i = 0; read_pos(fd, rootDirectoryOffset + i * entrySize,
105 			&entry, entrySize) == (ssize_t)entrySize; i++) {
106 		if (entry.type == EXFAT_ENTRY_TYPE_NOT_IN_USE
107 			|| entry.type == EXFAT_ENTRY_TYPE_LABEL) {
108 			if (get_volume_name(&entry, cookie->name, sizeof(cookie->name))
109 					!= B_OK) {
110 				delete cookie;
111 				return -1;
112 			}
113 			break;
114 		}
115 	}
116 
117 	if (cookie->name[0] == '\0') {
118 		off_t fileSystemSize = (off_t)superBlock.NumBlocks()
119 			<< superBlock.BlockShift();
120 		get_default_volume_name(fileSystemSize, cookie->name,
121 			sizeof(cookie->name));
122 	}
123 
124 	*_cookie = cookie;
125 	return 0.8f;
126 }
127 
128 
129 static status_t
130 exfat_scan_partition(int fd, partition_data* partition, void* _cookie)
131 {
132 	identify_cookie* cookie = (identify_cookie*)_cookie;
133 
134 	partition->status = B_PARTITION_VALID;
135 	partition->flags |= B_PARTITION_FILE_SYSTEM;
136 	partition->content_size = cookie->super_block.NumBlocks()
137 		<< cookie->super_block.BlockShift();
138 	partition->block_size = 1 << cookie->super_block.BlockShift();
139 	partition->content_name = strdup(cookie->name);
140 
141 	return partition->content_name != NULL ? B_OK : B_NO_MEMORY;
142 }
143 
144 
145 static void
146 exfat_free_identify_partition_cookie(partition_data* partition, void* _cookie)
147 {
148 	delete (identify_cookie*)_cookie;
149 }
150 
151 
152 //	#pragma mark -
153 
154 
155 static status_t
156 exfat_mount(fs_volume* _volume, const char* device, uint32 flags,
157 	const char* args, ino_t* _rootID)
158 {
159 	Volume* volume = new(std::nothrow) Volume(_volume);
160 	if (volume == NULL)
161 		return B_NO_MEMORY;
162 
163 	// TODO: this is a bit hacky: we can't use publish_vnode() to publish
164 	// the root node, or else its file cache cannot be created (we could
165 	// create it later, though). Therefore we're using get_vnode() in Mount(),
166 	// but that requires us to export our volume data before calling it.
167 	_volume->private_volume = volume;
168 	_volume->ops = &gExfatVolumeOps;
169 
170 	status_t status = volume->Mount(device, flags);
171 	if (status != B_OK) {
172 		ERROR("Failed mounting the volume. Error: %s\n", strerror(status));
173 		delete volume;
174 		return status;
175 	}
176 
177 	*_rootID = volume->RootNode()->ID();
178 	return B_OK;
179 }
180 
181 
182 static status_t
183 exfat_unmount(fs_volume *_volume)
184 {
185 	Volume* volume = (Volume *)_volume->private_volume;
186 
187 	status_t status = volume->Unmount();
188 	delete volume;
189 
190 	return status;
191 }
192 
193 
194 static status_t
195 exfat_read_fs_info(fs_volume* _volume, struct fs_info* info)
196 {
197 	Volume* volume = (Volume*)_volume->private_volume;
198 
199 	// File system flags
200 	info->flags = B_FS_IS_PERSISTENT
201 		| (volume->IsReadOnly() ? B_FS_IS_READONLY : 0);
202 	info->io_size = EXFAT_IO_SIZE;
203 	info->block_size = volume->BlockSize();
204 	info->total_blocks = volume->SuperBlock().NumBlocks();
205 	info->free_blocks = 0; //volume->NumFreeBlocks();
206 
207 	// Volume name
208 	strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name));
209 
210 	// File system name
211 	strlcpy(info->fsh_name, "exfat", sizeof(info->fsh_name));
212 
213 	return B_OK;
214 }
215 
216 
217 //	#pragma mark -
218 
219 
220 static status_t
221 exfat_get_vnode(fs_volume* _volume, ino_t id, fs_vnode* _node, int* _type,
222 	uint32* _flags, bool reenter)
223 {
224 	TRACE("get_vnode %" B_PRIdINO "\n", id);
225 	Volume* volume = (Volume*)_volume->private_volume;
226 
227 	Inode* inode = new(std::nothrow) Inode(volume, id);
228 	if (inode == NULL)
229 		return B_NO_MEMORY;
230 
231 	status_t status = inode->InitCheck();
232 	if (status != B_OK)
233 		delete inode;
234 
235 	if (status == B_OK) {
236 		_node->private_node = inode;
237 		_node->ops = &gExfatVnodeOps;
238 		*_type = inode->Mode();
239 		*_flags = 0;
240 	} else
241 		ERROR("get_vnode: InitCheck() failed. Error: %s\n", strerror(status));
242 
243 	return status;
244 }
245 
246 
247 static status_t
248 exfat_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter)
249 {
250 	delete (Inode*)_node->private_node;
251 	return B_OK;
252 }
253 
254 
255 static bool
256 exfat_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie)
257 {
258 	return true;
259 }
260 
261 
262 static status_t
263 exfat_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie,
264 	off_t pos, const iovec* vecs, size_t count, size_t* _numBytes)
265 {
266 	Volume* volume = (Volume*)_volume->private_volume;
267 	Inode* inode = (Inode*)_node->private_node;
268 
269 	if (inode->FileCache() == NULL)
270 		return B_BAD_VALUE;
271 
272 	rw_lock_read_lock(inode->Lock());
273 
274 	uint32 vecIndex = 0;
275 	size_t vecOffset = 0;
276 	size_t bytesLeft = *_numBytes;
277 	status_t status;
278 
279 	while (true) {
280 		file_io_vec fileVecs[8];
281 		size_t fileVecCount = 8;
282 
283 		status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs,
284 			&fileVecCount, 0);
285 		if (status != B_OK && status != B_BUFFER_OVERFLOW)
286 			break;
287 
288 		bool bufferOverflow = status == B_BUFFER_OVERFLOW;
289 
290 		size_t bytes = bytesLeft;
291 		status = read_file_io_vec_pages(volume->Device(), fileVecs,
292 			fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes);
293 		if (status != B_OK || !bufferOverflow)
294 			break;
295 
296 		pos += bytes;
297 		bytesLeft -= bytes;
298 	}
299 
300 	rw_lock_read_unlock(inode->Lock());
301 
302 	return status;
303 }
304 
305 
306 static status_t
307 exfat_io(fs_volume* _volume, fs_vnode* _node, void* _cookie,
308 	io_request* request)
309 {
310 	Volume* volume = (Volume*)_volume->private_volume;
311 	Inode* inode = (Inode*)_node->private_node;
312 
313 #ifndef EXFAT_SHELL
314 	if (io_request_is_write(request) && volume->IsReadOnly()) {
315 		notify_io_request(request, B_READ_ONLY_DEVICE);
316 		return B_READ_ONLY_DEVICE;
317 	}
318 #endif
319 
320 	if (inode->FileCache() == NULL) {
321 #ifndef EXFAT_SHELL
322 		notify_io_request(request, B_BAD_VALUE);
323 #endif
324 		return B_BAD_VALUE;
325 	}
326 
327 	// We lock the node here and will unlock it in the "finished" hook.
328 	rw_lock_read_lock(inode->Lock());
329 
330 	return do_iterative_fd_io(volume->Device(), request,
331 		iterative_io_get_vecs_hook, iterative_io_finished_hook, inode);
332 }
333 
334 
335 static status_t
336 exfat_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset,
337 	size_t size, struct file_io_vec* vecs, size_t* _count)
338 {
339 	TRACE("exfat_get_file_map()\n");
340 	Inode* inode = (Inode*)_node->private_node;
341 	size_t index = 0, max = *_count;
342 
343 	while (true) {
344 		off_t blockOffset;
345 		off_t blockLength;
346 		status_t status = inode->FindBlock(offset, blockOffset, &blockLength);
347 		if (status != B_OK)
348 			return status;
349 
350 		if (index > 0 && (vecs[index - 1].offset
351 				== blockOffset - vecs[index - 1].length)) {
352 			vecs[index - 1].length += blockLength;
353 		} else {
354 			if (index >= max) {
355 				// we're out of file_io_vecs; let's bail out
356 				*_count = index;
357 				return B_BUFFER_OVERFLOW;
358 			}
359 
360 			vecs[index].offset = blockOffset;
361 			vecs[index].length = blockLength;
362 			index++;
363 		}
364 
365 		offset += blockLength;
366 		size -= blockLength;
367 
368 		if ((off_t)size <= vecs[index - 1].length || offset >= inode->Size()) {
369 			// We're done!
370 			*_count = index;
371 			TRACE("exfat_get_file_map for inode %" B_PRIdINO"\n", inode->ID());
372 			return B_OK;
373 		}
374 	}
375 
376 	// can never get here
377 	return B_ERROR;
378 }
379 
380 
381 //	#pragma mark -
382 
383 
384 static status_t
385 exfat_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name,
386 	ino_t* _vnodeID)
387 {
388 	TRACE("exfat_lookup: name address: %p (%s)\n", name, name);
389 	Volume* volume = (Volume*)_volume->private_volume;
390 	Inode* directory = (Inode*)_directory->private_node;
391 
392 	// check access permissions
393 	status_t status = directory->CheckPermissions(X_OK);
394 	if (status < B_OK)
395 		return status;
396 
397 	status = DirectoryIterator(directory).Lookup(name, strlen(name), _vnodeID);
398 	if (status != B_OK) {
399 		ERROR("exfat_lookup: name %s (%s)\n", name, strerror(status));
400 		if (status == B_ENTRY_NOT_FOUND)
401 			entry_cache_add_missing(volume->ID(), directory->ID(), name);
402 		return status;
403 	}
404 
405 	TRACE("exfat_lookup: ID %" B_PRIdINO "\n", *_vnodeID);
406 	entry_cache_add(volume->ID(), directory->ID(), name, *_vnodeID);
407 
408 	return get_vnode(volume->FSVolume(), *_vnodeID, NULL);
409 }
410 
411 
412 static status_t
413 exfat_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd,
414 	void* buffer, size_t bufferLength)
415 {
416 	TRACE("ioctl: %" B_PRIu32 "\n", cmd);
417 
418 	/*Volume* volume = (Volume*)_volume->private_volume;*/
419 	return B_DEV_INVALID_IOCTL;
420 }
421 
422 
423 static status_t
424 exfat_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat)
425 {
426 	Inode* inode = (Inode*)_node->private_node;
427 
428 	stat->st_dev = inode->GetVolume()->ID();
429 	stat->st_ino = inode->ID();
430 	stat->st_nlink = 1;
431 	stat->st_blksize = EXFAT_IO_SIZE;
432 
433 	stat->st_uid = inode->UserID();
434 	stat->st_gid = inode->GroupID();
435 	stat->st_mode = inode->Mode();
436 	stat->st_type = 0;
437 
438 	inode->GetAccessTime(stat->st_atim);
439 	inode->GetModificationTime(stat->st_mtim);
440 	inode->GetChangeTime(stat->st_ctim);
441 	inode->GetCreationTime(stat->st_crtim);
442 
443 	stat->st_size = inode->Size();
444 	stat->st_blocks = (inode->Size() + 511) / 512;
445 
446 	return B_OK;
447 }
448 
449 
450 static status_t
451 exfat_open(fs_volume* /*_volume*/, fs_vnode* _node, int openMode,
452 	void** _cookie)
453 {
454 	Inode* inode = (Inode*)_node->private_node;
455 
456 	// opening a directory read-only is allowed, although you can't read
457 	// any data from it.
458 	if (inode->IsDirectory() && (openMode & O_RWMASK) != 0)
459 		return B_IS_A_DIRECTORY;
460 
461 	status_t status =  inode->CheckPermissions(open_mode_to_access(openMode)
462 		| (openMode & O_TRUNC ? W_OK : 0));
463 	if (status != B_OK)
464 		return status;
465 
466 	// Prepare the cookie
467 	file_cookie* cookie = new(std::nothrow) file_cookie;
468 	if (cookie == NULL)
469 		return B_NO_MEMORY;
470 	ObjectDeleter<file_cookie> cookieDeleter(cookie);
471 
472 	cookie->open_mode = openMode & EXFAT_OPEN_MODE_USER_MASK;
473 	cookie->last_size = inode->Size();
474 	cookie->last_notification = system_time();
475 
476 	if ((openMode & O_NOCACHE) != 0 && inode->FileCache() != NULL) {
477 		// Disable the file cache, if requested?
478 		status = file_cache_disable(inode->FileCache());
479 		if (status != B_OK)
480 			return status;
481 	}
482 
483 	cookieDeleter.Detach();
484 	*_cookie = cookie;
485 
486 	return B_OK;
487 }
488 
489 
490 static status_t
491 exfat_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos,
492 	void* buffer, size_t* _length)
493 {
494 	Inode* inode = (Inode*)_node->private_node;
495 
496 	if (!inode->IsFile()) {
497 		*_length = 0;
498 		return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE;
499 	}
500 
501 	return inode->ReadAt(pos, (uint8*)buffer, _length);
502 }
503 
504 
505 static status_t
506 exfat_close(fs_volume *_volume, fs_vnode *_node, void *_cookie)
507 {
508 	return B_OK;
509 }
510 
511 
512 static status_t
513 exfat_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie)
514 {
515 	file_cookie* cookie = (file_cookie*)_cookie;
516 	Volume* volume = (Volume*)_volume->private_volume;
517 	Inode* inode = (Inode*)_node->private_node;
518 
519 	if (inode->Size() != cookie->last_size)
520 		notify_stat_changed(volume->ID(), -1, inode->ID(), B_STAT_SIZE);
521 
522 	delete cookie;
523 	return B_OK;
524 }
525 
526 
527 static status_t
528 exfat_access(fs_volume* _volume, fs_vnode* _node, int accessMode)
529 {
530 	Inode* inode = (Inode*)_node->private_node;
531 	return inode->CheckPermissions(accessMode);
532 }
533 
534 
535 static status_t
536 exfat_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer,
537 	size_t *_bufferSize)
538 {
539 	Inode* inode = (Inode*)_node->private_node;
540 
541 	if (!inode->IsSymLink())
542 		return B_BAD_VALUE;
543 
544 	status_t result = inode->ReadAt(0, reinterpret_cast<uint8*>(buffer),
545 		_bufferSize);
546 	if (result != B_OK)
547 		return result;
548 
549 	*_bufferSize = inode->Size();
550 
551 	return B_OK;
552 }
553 
554 
555 //	#pragma mark - Directory functions
556 
557 
558 static status_t
559 exfat_open_dir(fs_volume* /*_volume*/, fs_vnode* _node, void** _cookie)
560 {
561 	Inode* inode = (Inode*)_node->private_node;
562 	status_t status = inode->CheckPermissions(R_OK);
563 	if (status < B_OK)
564 		return status;
565 
566 	if (!inode->IsDirectory())
567 		return B_NOT_A_DIRECTORY;
568 
569 	DirectoryIterator* iterator = new(std::nothrow) DirectoryIterator(inode);
570 	if (iterator == NULL || iterator->InitCheck() != B_OK) {
571 		delete iterator;
572 		return B_NO_MEMORY;
573 	}
574 
575 	*_cookie = iterator;
576 	return B_OK;
577 }
578 
579 
580 static status_t
581 exfat_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie,
582 	struct dirent *dirent, size_t bufferSize, uint32 *_num)
583 {
584 	TRACE("exfat_read_dir\n");
585 	DirectoryIterator* iterator = (DirectoryIterator*)_cookie;
586 	Volume* volume = (Volume*)_volume->private_volume;
587 
588 	uint32 maxCount = *_num;
589 	uint32 count = 0;
590 
591 	while (count < maxCount && bufferSize > sizeof(struct dirent)) {
592 		ino_t id;
593 		size_t length = bufferSize - offsetof(struct dirent, d_name);
594 
595 		status_t status = iterator->GetNext(dirent->d_name, &length, &id);
596 		if (status == B_ENTRY_NOT_FOUND)
597 			break;
598 
599 		if (status == B_BUFFER_OVERFLOW) {
600 			// the remaining name buffer length was too small
601 			if (count == 0)
602 				return B_BUFFER_OVERFLOW;
603 			break;
604 		}
605 
606 		if (status != B_OK)
607 			return status;
608 
609 		dirent->d_dev = volume->ID();
610 		dirent->d_ino = id;
611 		dirent->d_reclen = offsetof(struct dirent, d_name) + length + 1;
612 
613 		bufferSize -= dirent->d_reclen;
614 		dirent = (struct dirent*)((uint8*)dirent + dirent->d_reclen);
615 		count++;
616 	}
617 
618 	*_num = count;
619 	TRACE("exfat_read_dir end\n");
620 	return B_OK;
621 }
622 
623 
624 static status_t
625 exfat_rewind_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void *_cookie)
626 {
627 	DirectoryIterator* iterator = (DirectoryIterator*)_cookie;
628 
629 	return iterator->Rewind();
630 }
631 
632 
633 static status_t
634 exfat_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/)
635 {
636 	return B_OK;
637 }
638 
639 
640 static status_t
641 exfat_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie)
642 {
643 	delete (DirectoryIterator*)_cookie;
644 	return B_OK;
645 }
646 
647 
648 fs_volume_ops gExfatVolumeOps = {
649 	&exfat_unmount,
650 	&exfat_read_fs_info,
651 	NULL,	// write_fs_info()
652 	NULL,	// fs_sync,
653 	&exfat_get_vnode,
654 };
655 
656 
657 fs_vnode_ops gExfatVnodeOps = {
658 	/* vnode operations */
659 	&exfat_lookup,
660 	NULL,
661 	&exfat_put_vnode,
662 	NULL,	// exfat_remove_vnode,
663 
664 	/* VM file access */
665 	&exfat_can_page,
666 	&exfat_read_pages,
667 	NULL,	// exfat_write_pages,
668 
669 	NULL,	// io()
670 	NULL,	// cancel_io()
671 
672 	&exfat_get_file_map,
673 
674 	&exfat_ioctl,
675 	NULL,
676 	NULL,	// fs_select
677 	NULL,	// fs_deselect
678 	NULL,	// fs_fsync,
679 
680 	&exfat_read_link,
681 	NULL,	// fs_create_symlink,
682 
683 	NULL,	// fs_link,
684 	NULL,	// fs_unlink,
685 	NULL,	// fs_rename,
686 
687 	&exfat_access,
688 	&exfat_read_stat,
689 	NULL,	// fs_write_stat,
690 	NULL,	// fs_preallocate
691 
692 	/* file operations */
693 	NULL,	// fs_create,
694 	&exfat_open,
695 	&exfat_close,
696 	&exfat_free_cookie,
697 	&exfat_read,
698 	NULL,	//	fs_write,
699 
700 	/* directory operations */
701 	NULL, 	// fs_create_dir,
702 	NULL, 	// fs_remove_dir,
703 	&exfat_open_dir,
704 	&exfat_close_dir,
705 	&exfat_free_dir_cookie,
706 	&exfat_read_dir,
707 	&exfat_rewind_dir,
708 
709 	/* attribute directory operations */
710 	NULL, 	// fs_open_attr_dir,
711 	NULL,	// fs_close_attr_dir,
712 	NULL,	// fs_free_attr_dir_cookie,
713 	NULL,	// fs_read_attr_dir,
714 	NULL,	// fs_rewind_attr_dir,
715 
716 	/* attribute operations */
717 	NULL,	// fs_create_attr,
718 	NULL,	// fs_open_attr,
719 	NULL,	// fs_close_attr,
720 	NULL,	// fs_free_attr_cookie,
721 	NULL,	// fs_read_attr,
722 	NULL,	// fs_write_attr,
723 	NULL,	// fs_read_attr_stat,
724 	NULL,	// fs_write_attr_stat,
725 	NULL,	// fs_rename_attr,
726 	NULL,	// fs_remove_attr,
727 };
728 
729 
730 static file_system_module_info sExfatFileSystem = {
731 	{
732 		"file_systems/exfat" B_CURRENT_FS_API_VERSION,
733 		0,
734 		NULL,
735 	},
736 
737 	"exfat",						// short_name
738 	"ExFAT File System",			// pretty_name
739 	0,								// DDM flags
740 
741 	// scanning
742 	exfat_identify_partition,
743 	exfat_scan_partition,
744 	exfat_free_identify_partition_cookie,
745 	NULL,	// free_partition_content_cookie()
746 
747 	&exfat_mount,
748 
749 	NULL,
750 };
751 
752 
753 module_info *modules[] = {
754 	(module_info *)&sExfatFileSystem,
755 	NULL,
756 };
757